Firebase Authentication - REST API NodeJS - node.js

What's the difference between using
admin.auth().verifyIdToken() and admin.auth().createSessionCookie() + admin.auth().verifySessionCookie() for authentication purposes and which one should I use in my Express REST API?
Also, doesn't the verifyIdToken already create a session itself that can be refreshed everytime it is called? And does verifying the session cookie do the same?

You create the session to get a token on the client device and use the verify token on the server/cloud.
I get the token from the current user then send it to firebase cloud functions endpoint to verify it.
Endpoint
import * as admin from 'firebase-admin'
const DEPLOYED = false;
admin.initializeApp()
const ValidateToken = (request: any, response: any) => {
const params = {
a: request.body.token, // Client Validation
}
const ValidateToken = admin.auth().verifyIdToken(params.a).catch((error) => { throw { Message:error }});
return Promise.all([ValidateToken]).then((res: any) => {
return DEPLOYED ? res : response.status(200).json(res);
}).catch(error => {
return DEPLOYED ? error : response.status(400).json(error);
});
}
export default ValidateToken;

Related

How to check wheter the Azure AD token send by React to Node.js is valid

Hi I have a code from https://github.com/Azure-Samples/ms-identity-javascript-react-spa
I changed it a little bit, so instead calling an Microsoft Graph API endpoint, I call mine endpoint on localhost:7000.
So it basically starts with me logging in (here i did not change enything). Then there is this function which acquires token:
const { instance, accounts } = useMsal();
const [graphData, setData] = useState(null);
function RequestProfileData() {
// Silently acquires an access token which is then attached to a request for MS Graph data
instance
.acquireTokenSilent({
...loginRequest,
account: accounts[0],
})
.then((response) => {
callMyEndpoint(response.accessToken).then((response) =>
setData(response)
);
});
}
it uses function callMyEndpoint which looks like this:
export async function callMyEndpoint(accessToken) {
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
headers.append("Authorization", bearer);
const options = {
method: "POST",
headers: headers,
};
return fetch("http://localhost:7000/myendpoint", options)
.then((response) => response.json())
.catch((error) => console.log(error)) // if the user is not logged in- catch an error;
}
Now, onto my Node.js backend application where the http://localhost:7000/myendpoint is served.
app.post("/myendpoint", async (req, res) => {
console.log("TOKEN", req.headers.authorization); // it is being printed here, everything seems fine.
// here i would like to check whether the token is valid
// if req.headers.authorization == AZURE_TOKEN?
// How to do this?
});
And now the question is? How to check in backend if the token send from frontend is valid for the user, so only logged users, or users which are added in my app registration in azure can post onto this request?
You can use the libraries such as validate-azure-ad-token or you can write your own logic using jsonwebtoken
Here I have my custom logic for that first you will need client_id , tenat_id and scope name.
I am assuming you already have client and tenant id and for scope name it will be available in the Expose Api tab of your app registration.
Here I have console app which will take your token and try to validate it.
var jwt = require('jsonwebtoken');
var token = 'your Token';
var clientid = '' ;
var tenantid = "" ;
var scope = "";
// Create an audiance variable
var audiance = 'api://'+clientid;
// decoded token
var decodedToken = jwt.decode(token , {complete :true});
if((decodedToken.payload.aud==audi)&&(decodedToken.payload.scp==scope)&&(decodedToken.payload.tid==tenantid))
{
console.log("The token is valid");
}
else
{
console.log("The Token is invalid")
}
Output :

Required claim nbf not present in token (using Firebase Microsoft Signin trying to access MicrosoftGraph)

I currently have an app with the following structure: Angular front-end, Node.js server.
We have implemented Google Cloud's Identity Providers to sign in using Google and/or Microsoft.
Google Sign-in and access the Google Cloud Admin SDK working perfectly, however trying to access Microsoft Graph is giving the following error:
UnhandledPromiseRejectionWarning: Error: Required claim nbf not present in token
According to Firebase documentation you can use the access token recieved from the Signin to access Graph:
Firebase documentation screenshot
On successful completion, the OAuth access token associated with the provider can be retrieved from the firebase.auth.UserCredential object returned. Using the OAuth access token, you can call the Microsoft Graph API. For example, to get the basic profile information, the following REST API can be called:
curl -i -H "Authorization: Bearer ACCESS_TOKEN" https://graph.microsoft.com/v1.0/me
When running the above in url I get the same error:
{"error":{"code":"InvalidAuthenticationToken","message":"Required claim nbf not present in token","innerError":{"date":"2022-05-26T12:51:11","request-id":"##########","client-request-id":"##########"}}}
I'm using the signInWithPopup in my authentication service (authentication.service.ts):
await signInWithPopup(this.auth, provider)
.then((result) => {
const credential = OAuthProvider.credentialFromResult(result);
const accessToken = credential.accessToken;
const idToken = credential.idToken;
this.setCurrentUser(result.user);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
const email = error.email;
const credential = OAuthProvider.credentialFromError(error);
console.log(error);
});
I send the accessToken to my server (app.ts):
app.get(
'/api/microsoft-get-organisation',
async (req: express.Request, res: express.Response) => {
//https://graph.microsoft.com/v1.0/organization
const organisation = await ms.getOrganisation(req.headers, '/organization');
res.send(JSON.stringify(organisation));
}
);
export const getOrganisation = async (headers: any, graphEndpoint: string) => {
const client = await getAuthenticatedClient(headers);
return await client.api(graphEndpoint).get();
};
async function getAuthenticatedClient(headers: any) {
const client = await graph.Client.init({
authProvider: (done: any) => {
done(null, headers.authorization.split(' ')[1]);
},
});
return client;
}
When verifying the token I can see that there is no nbf claim:
token screen shot
Any advice on what I have done wrong so that I can access Microsoft Graph?

Passing Keycloak bearer token to express backend?

We have a frontend application that uses Vue3 and a backend that uses nodejs+express.
We are trying to make it so once the frontend application is authorised by keycloak it can then pass a bearer token to the backend (which is also protected by keycloak in the same realm), to make the API calls.
Can anyone suggest how we should be doing this?
Follows is what we are trying and seeing as a result.
The error thrown back is simply 'Access Denied', with no other details Running the debugger we see a 'invalid token (wrong audience)' error thrown in the GrantManager.validateToken function (which unfortunately doesn't bubble up).
The frontend makes use of #dsb-norge/vue-keycloak-js which leverages keycloak-js.
The backend makes use of keycloak-connect. Its endpoints are REST based.
In the webapp startup we initialise axios as follows, which passes the bearer token to the backend server
const axiosConfig: AxiosRequestConfig = {
baseURL: 'http://someurl'
};
api = axios.create(axiosConfig);
// include keycloak token when communicating with API server
api.interceptors.request.use(
(config) => {
if (app.config.globalProperties.$keycloak) {
const keycloak = app.config.globalProperties.$keycloak;
const token = keycloak.token as string;
const auth = 'Authorization';
if (token && config.headers) {
config.headers[auth] = `Bearer ${token}`;
}
}
return config;
}
);
app.config.globalProperties.$api = api;
On the backend, during the middleware initialisation:
const keycloak = new Keycloak({});
app.keycloak = keycloak;
app.use(keycloak.middleware({
logout: '/logout',
admin: '/'
}));
Then when protecting the endpoints:
const keycloakJson = keystore.get('keycloak');
const keycloak = new KeycloakConnect ({
cookies: false
}, keycloakJson);
router.use('/api', keycloak.protect('realm:staff'), apiRoutes);
We have two client configured in Keycloak:
app-frontend, set to use access type 'public'
app-server, set to use access type 'bearer token'
Trying with $keycloak.token gives us the 'invalid token (wrong audience)' error, but if we try with $keycloak.idToken instead, then we get 'invalid token (wrong type)'
In the first case it is comparing token.content.aud of value 'account', with a clientId of app-server. In the second case it is comparing token.content.typ, of value 'ID' with an expected type of 'Bearer'.
Upon discussion with a developer on another projects, it turns out my approach is wrong on the server and that keycloak-connect is the wrong tool for the job. The reasoning is that keycloak-connect is wanting to do its own authentication flow, since the front-end token is incompatible.
The suggested approach is to take the bearer token provided in the header and use the jwt-uri for my keycloak realm to verify the token and then use whatever data I need in the token.
Follows is an early implementation (it works, but it needs refinement) of the requireApiAuthentication function I am using to protect our endpoints:
import jwksClient from 'jwks-rsa';
import jwt, { Secret, GetPublicKeyOrSecret } from 'jsonwebtoken';
// promisify jwt.verify, since it doesn't do promises
async function jwtVerify (token: string, secretOrPublicKey: Secret | GetPublicKeyOrSecret): Promise<any> {
return new Promise<any>((resolve, reject) => {
jwt.verify(token, secretOrPublicKey, (err: any, decoded: object | undefined) => {
if (err) {
reject(err);
} else {
resolve(decoded);
}
});
});
}
function requireApiAuthentication (requiredRole: string) {
// TODO build jwksUri based on available keycloak configuration;
const baseUrl = '...';
const realm = '...';
const client = jwksClient({
jwksUri: `${baseUrl}/realms/${realm}/protocol/openid-connect/certs`
});
function getKey (header, callback) {
client.getSigningKey(header.kid, (err: any, key: Record<string, any>) => {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
return async (req: Request, res: Response, next: NextFunction) => {
const authorization = req.headers.authorization;
if (authorization && authorization.toLowerCase().startsWith('bearer ')) {
const token = authorization.split(' ')[1];
const tokenDecoded = await jwtVerify(token, getKey);
if (tokenDecoded.realm_access && tokenDecoded.realm_access.roles) {
const roles = tokenDecoded.realm_access.roles;
if (roles.indexOf(requiredRole) > -1) {
next();
return;
}
}
}
next(new Error('Unauthorized'));
};
}
and then used as follows:
router.use('/api', requireApiAuthentication('staff'), apiRoutes);

Is it possible to integrate bot-builder into an existing express app?

I have an existing node/express chatbot application that connects to several chat platforms using ExpressJS' next(), next() middleware design pattern. I send a 200 response at the very beginning to acknowledge the receipt of a message, and send a new POST request to send a message from my last middleware.
app.post("/bots", receiveMsg, doStuff, formatAndSendMsg, catchErrors);
Now I want to integrate Skype as a channel for my bot, but the NodeJS library for bot-framework has a totally different way of doing things, using events and such magic that I haven't fully understood yet:
var connector = new builder.ConsoleConnector();
app.post("/skype", connector.listen());
var bot = new builder.UniversalBot(connector, function (session) {
session.send("You said: %s", session.message.text);
});
It doesn't look like these are compatible ways to do things, so what is the best way to receive a message and then send a response to a user without having to change my express routing to fit bot-builder in? Can I get a Session object with Session.send() to respond to? Will I have to do all the addressing manually? Is there a method that resembles this:
app.post("/skype", (req, res, next) => {
const address = req.body.id;
const message = new builder.Message(address, messageBody).send()
}
Or:
app.post("/skype", connector.listen(), (req, res, next) => {
// (res.locals is available in every express middleware function)
const session = res.locals.botFrameworkSession;
// do stuff
session.send(message);
}
You can register bot application in your existing express applications. Bot builder SDK is also compatible in expressjs framework. You can refer to official sample which is also leveraging express.
Don't forget to modify the messsaging endpoint in your bot registration to your bot's endpoint, e.g.
https://yourdomain/stuff
in your scenario. Please refer to https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration for more info.
Building messages, addressing them, and sending those messages are all possible using the official bot framework NodeJS library. What I couldn't do with that library was receive messages and verify their authenticity on my routes without making major changes to my design (using request middleware - next() - to process the incoming request) which is already in production with other bots and not easy to change.
Here's my workaround: First is this BotFrameworkAuthenticator class that I create based on the Microsoft documentation here: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-authentication
It is instantiated with the appID and appPassword from your Bot Framework app.
import axios from "axios";
import * as jwt from "jsonwebtoken";
import * as jwkToPem from 'jwk-to-pem';
export class BotFrameworkAuthenticator {
private appId: string;
private appPassword: string;
private openIdMetadata: any;
// The response body specifies the document in the JWK format but also includes an additional property for each key: endorsements.
private validSigningKeys: any;
// The list of keys is relatively stable and may be cached for long periods of time (by default, 5 days within the Bot Builder SDK).
private signingKeyRefreshRate: number = 432000; // in seconds (432000 = 5 days)
constructor(appId, appPassword) {
this.appId = appId;
this.appPassword = appPassword;
this.getListOfSigningKeys();
}
// response data should contain "jwks_uri" property that contains address to request list of valid signing keys.
public async getOpenIdMetaData() {
// This is a static URL that you can hardcode into your application. - MS Bot Framework docs
await axios.get("https://login.botframework.com/v1/.well-known/openidconfiguration").then(response => {
this.openIdMetadata = response.data;
logger.info("OpenID metadata document recieved for Bot Framework.");
}).catch(err => {
logger.warn(err.message, "Could not get OpenID metadata document for Bot Framework. Retrying in 15 seconds...");
setTimeout(this.getListOfSigningKeys, 15000);
})
}
public async getListOfSigningKeys() {
await this.getOpenIdMetaData();
if (this.openIdMetadata && this.openIdMetadata.jwks_uri) {
// previous function getOpenIdMetaData() succeeded
await axios.get(this.openIdMetadata.jwks_uri).then(response => {
logger.info(`Signing keys recieved for Bot Framework. Caching for ${this.signingKeyRefreshRate / 86400} days.`);
this.validSigningKeys = response.data.keys;
setTimeout(this.getListOfSigningKeys, (this.signingKeyRefreshRate * 1000));
}).catch(err => {
logger.error(err.message, "Could not get list of valid signing keys for Bot Framework. Retrying in 15 seconds");
setTimeout(this.getListOfSigningKeys, 15000);
});
} else {
// previous function getOpenIdMetaData() failed, but has already queued this function to run again. Will continue until succeeds.
return;
}
}
/**
* Verifies that the message was sent from Bot Framework by checking values as specified in Bot Framework Documentation:
* https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-authentication#step-4-verify-the-jwt-token
* Retrieves the Bearer token from the authorization header, decodes the token so we can match the key id (kid) to a key in the OpenID
* document, then converts that key to PEM format so that jwt/crypto can consume it to verify that the bearer token is
* cryptographically signed.
* If the serviceUrl property in the token doe not match the serviceUrl property in the message, it should also be rejected.
*/
public verifyMsgAuthenticity(serviceUrl: string, headers: any) {
try {
const token = headers.authorization.replace("Bearer ", "");
const decoded = jwt.decode(token, { complete: true }) as any;
const verifyOptions = {
issuer: "https://api.botframework.com",
audience: this.appId,
clockTolerance: 300, // (seconds) The token is within its validity period. Industry-standard clock-skew is 5 minutes. (Bot Framework documentation);
}
const jwk = this.lookupKey(decoded.header.kid)
const pem = jwkToPem(jwk);
const verified = jwt.verify(token, pem, verifyOptions) as any;
if (!serviceUrl || serviceUrl !== verified.serviceurl) {
logger.warn("Non-matching serviceUrl in Bot Framework verified token!")
return false;
}
return true;
} catch (err) {
logger.warn("Received invalid/unsigned message on Bot Framework endpoint!", err.message)
return false;
}
}
// Finds the relevant key from the openID list. Does not transform the key.
private lookupKey(kid) {
const jwk = this.validSigningKeys.find((key) => {
return (key.kid === kid);
});
return jwk;
}
}
Use the BotFrameworkAuthenticator class like this at the very beginning of your express route to verify that all incoming requests are valid.
const botFrameworkAuthenticator = new BotFrameworkAuthenticator(appID, appPassword);
router.post("/", (req: Request, res: Response, next: NextFunction) => {
if (botFrameworkAuthenticator.verifyMsgAuthenticity(req.body.serviceUrl, req.headers) === true) {
res.status(200).send();
next();
} else {
// unsafe to process
res.status(403).send();
return;
}
});
And to send messages using the regular Bot Framework library without having a Session object that would normally be created by the Bot Framework library when it receives an incoming message:
import * as builder from "botbuilder";
// instantiate the chatConnector (only once, not in the same function as the sending occurs)
const botFrameworkSender = new builder.ChatConnector({ appId, appPassword });
//---------------------------------------------
const skypeMsg = req.body;
const address = {
channelId: skypeMsg.channelId,
user: skypeMsg.from,
bot: skypeMsg.recipient,
conversation: skypeMsg.conversation
};
const response = new builder.Message().text(someText).address(address).toMessage();
const formattedResponses = [response];
botFrameworkSender.send(formattedResponses, logErrorsToConsole);
Note that all of the builder.Message() -- .attachment(), .images(), etc.. -- functions can be used, not just the text()

Firebase 3.0 Tokens : [Error: Invalid claim 'kid' in auth header.]

I'm trying to create JWT tokens in node.js for use with the REST api in firebase, but when I try to use them, I get the error "Error: Invalid claim 'kid' in auth header."
This is my code
http.createServer(function (req, res) {
var payload = {
uid: "bruh"
};
var token = jwt.sign(payload, sact["private_key"], {
algorithm: 'RS256',
issuer: sact["client_email"],
subject: sact["client_email"],
audience: 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
expiresIn: '3600s',
jwtid: sact["private_key_id"],
header: {
"kid": sact["private_key_id"]
}
});
res.writeHead(200);
res.end("It worked. (" + token + ")");
}).listen(port);
These are my requires
var http = require('http');
var jwt = require('jsonwebtoken');
Please use returnSecureToken: true, with correct Spellings
I hope it will solve the problem of Invalid claim 'kid' in the auth header.
This is an issue because you're generating a Firebase ID token, not an access token for the Firebase REST API.
To generate a REST API token I would use the legacy Firebase Token Generator library which still works perfectly well (but only generates REST tokens, not general purpose access tokens).
Note that your Firebase Database secret is now located under the gear icon in the top left of the console.
So I had this error and I've fixed it. Now here is the solution:
You'll need to retrieve the ID-token using an additional function. Here is the function you can use:
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
I implemented it somewhat like this:
//google OAuth login handler
const googleLoginHandler = () => {
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
setgoogleAuthStatus(true)
// The signed-in user info.
const userId = result.user.uid;
const displayName = result.user.displayName;
const email = result.user.email;
//This is the function for getting the ID-Token
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then((idToken) => {
// Send token to your backend via HTTPS
console.log(idToken)
}).catch((error) => {
// Handle error
console.log(error.message)
alert(error.message)
});
console.log(result)
}).catch((error) => {
console.log(error)
// Handle Errors here.
alert(error.message)
})
}
The id token you get by this method can be used to access the firebase real-time database and other firebase services.
check out these links for more details:
https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients
https://firebase.google.com/docs/database/rest/auth#firebase_id_tokens

Resources