Firestore Custom Tokens - node.js

I am trying to use a custom auth token with firestore. I am using nodejs to generate the token with the following code.
const admin = require('firebase-admin');
const serviceAccount = require('./ServiceAccountKey.json')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
var uid = "some-uid";
var claim = {
control: true
};
admin.auth().createCustomToken(uid, true)
.then(function(customToken) {
console.log(customToken)
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
When I run it I get a token. I take that token and try it out using
https://firestore.googleapis.com/v1beta1/projects/example-project-5caa9/databases/(default)/documents/users with the headers
Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiY29udHJvbCI6dHJ1ZX0sInVpZCI6InNvbWUtdWlkIiwiaWF0IjoxNTI4MTQ0NzY3LCJleHAiOjE1MjgxNDgzNjcsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHl0b29sa2l0Lmdvb2dsZWFwaXMuY29tL2dvb2dsZS5pZGVudGl0eS5pZGVudGl0eXRvb2xraXQudjEuSWRlbnRpdHlUb29sa2l0IiwiaXNzIjoiZmlyZWJhc2UtYWRtaW5zZGsteG9jMDRAZXhhbXBsZS1wcm9qZWN0LTVjYWE5LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic3ViIjoiZmlyZWJhc2UtYWRtaW5zZGsteG9jMDRAZXhhbXBsZS1wcm9qZWN0LTVjYWE5LmlhbS5nc2VydmljZWFjY291bnQuY29tIn0.Bjl6VY5CZKIpNyCayROWr_ZBSRmo11hiwtnx_cbbw2Ggk3J2x0Ml2OkpXhU-vAD6Q53fCZwGgXeCdxnsXw0lr55cJH3Q6J7gitzQoRnfJgUX9Dv1gbI90OWashxMmxtzPIpwgSnfBv61mkdv9ZVrF8o362mQBx_LUQzvGgVPEN9_9UNCH7peOS4KYr_YRMpCQVem0XMNh9WKlyBZuScjHpY6dZZhXqOHda0W9-MNAfvQ-D0pt-osq4ty-D_WYk6CjLNmxzvHoZeoIk1YShJM4Mpyec3lXFcCXNYG2c3_r2tskTB0LF7Fc7Bg5XuJwlrAzHrnRis6iZFCx8sqH1b-Zg
get the following JSON.
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
My rules are as follow
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}

You can't directly access REST APIs with a custom token. You need to sign in using the custom token, and obtain an ID token. There are 2 ways to do this:
Sign in from a Firebase Client SDK
Use the Firebase Auth REST API to exchange the custom token to an ID token.
Then you can access the Firestore REST API with the resulting ID token:
https://firebase.google.com/docs/firestore/use-rest-api#working_with_firebase_id_tokens

Related

AuthenticationFailed when authenticating via nodejs app and package #azure/msal-node

I have an Azure app registered . I am trying to authenticate to that app . I am able to do that and successfully get the accesstoken and idtoken.
However, when I use that token and try to make a request to list subscriptions API (https://management.azure.com/subscriptions?api-version=2020-01-01) , the request fails and give response "AuthenticationFailed". I have also tried changing the scope to https://management.azure.com/.default but the same error is there. Below is the nodejs code and I am also attaching the API permissions of app
const config = {
auth: {
clientId: 'xxx',
authority: 'https://login.microsoftonline.com/organizations',
clientSecret: 'yyy',
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
},
},
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
app.get('/', (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read','https://management.azure.com/user_impersonation'],
redirectUri: REDIRECT_URI,
};
// get url to sign user in and consent to scopes needed for application
pca
.getAuthCodeUrl(authCodeUrlParameters)
.then((response) => {
res.redirect(response);
})
.catch((error) => console.log(JSON.stringify(error)));
});
The response I am getting is
{
"error": {
"code": "AuthenticationFailed",
"message": "Authentication failed."
}
}
The error "AuthenticationFailed" usually occurs if you are using different scope token to call the API.
I tried to generate access token with the same scope as you
via Postman and got the same error while calling the query like below:
Please note that,
user.read audience is Microsoft Graph API
https://management.azure.com/user_impersonation audience is Azure Service Management.
As you have given two different scopes with different audiences, it will consider the first scope (user.read) to generate the token as mentioned in this SO Thread which was solved by me.
When you call the query https://management.azure.com/subscriptions?api-version=2020-01-01 with the above token, you will get the error as it is intended for MS Graph audience.
I tried to generate the token with scope https://management.azure.com/user_impersonation only, removing user.read like below:
With the above generated token, I am able to call the API successfully like below:
If you want token with different scopes, then you have to generate two access tokens separately.

Teams bot SSO without dialogs framework. Invalid x5t claim

I'm following this tutorial:
https://learn.microsoft.com/en-us/learn/modules/msteams-sso/7-exercise-bots-sso
https://github.com/OfficeDev/TrainingContent/tree/master/Teams/80%20Using%20Single%20Sign-On%20with%20Microsoft%20Teams/Demos/02-learn-msteams-sso-bot
https://youtu.be/cmI06T2JLEg
https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/javascript_nodejs/24.bot-authentication-msgraph
The bot worked as expected. But I would like not to use the dialog framework. I'm having trouble adapting the model.
In the personal scope I reply to a message with an oauth card:
const oauthCard = await CardFactory.oauthCard(SsoConnectionName, undefined, undefined, undefined, {
id: 'random65jHf9276hDy47',
uri: `api://botid-${MicrosoftAppId}`
})
await context.sendActivity(MessageFactory.attachment(oauthCard))
so i get the token on the handler
handleTeamsSigninTokenExchange(context, query) {
if (context?.activity?.name === tokenExchangeOperationName) {
console.dir(context?.activity?.value)
token = context?.activity?.value?.token
}
}
What am I supposed to do with this token? I get the Invalid x5t claim error when I try to use microsoft client like this:
msGraphClient = microsoft.Client.init({
debugLogging: true,
authProvider: done => {
done(null, token)
}
})
// on message 'whoiam'
const me = await msGraphClient.api("me").get()
Is this the correct token? How do I initialize the Microsoft Graph client with this token?
My repo sample: https://github.com/itacirgabral/teamsbotSSOdemo/blob/nodialog/nodialogs.js
You can use below code snippet for initializing the Graph Client:
// Get an Authenticated Microsoft Graph client using the token issued to the user.
this.graphClient = Client.init({
authProvider: (done) => {
done(null, this._token); // First parameter takes an error if you can't get an access token.
}
});
Refence sample link:
https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/app-sso/nodejs/server/models/simpleGraphClient.js
https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/app-sso/nodejs/server/api/appController.js

API route permissions using an express-jwt-permissions guard on node.js

I am attempting to use express-jwt-permissions to protect an API route and I am unable to use the guard syntax "guard.check('user')". I have successfully used express-jwt which express-jwt-permissions builds upon.
An interesting fact is that express-jwt requires the JWT_SECRET to be assigned to it, whereas there are no instruction on the express-jwt-permissions docs to understand this interplay or perhaps there is some example missing?
My current code is as follows:
/////////////////////////////////////////////
// auth.ts - Auth and set user permissions
/////////////////////////////////////////////
router.post('/', async (request, response, next) => {
const {email, password} = request.body
try {
// Authenticate
const user = await authenticate(email, password)
user.permissions = ['user'] // set the express-jwt-permissions here
// Create JWT token
let token = jwt.sign(user.toJSON(), process.env.JWT_SECRET, {
expiresIn: '60m'
})
let {iat, exp} = jwtDecode(token)
// Respond with token
response.status(HttpStatus.OK).send({iat, exp, token})
...
I successfully retrieved a JWT token from the 'api/auth' endpoint.
{
"iat": 1559650778,
"exp": 1559654378,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwZXJtaXNzaW9ucyI6WyJ1c2VyIl0sIl9pZCI6IjVjZjUyZjc1NDA4MTk0YWI1MGZlMWNkNiIsIm5hbWUiOiJHYXJ5IFBhbHVrIiwiZW1haWwiOiJnYXJ5QHBsdWdpbi5pbyIsInVzZXJuYW1lIjoiZ2FyeSIsInBhc3N3b3JkIjoiJDJhJDEwJEt1U1NUQXowd1MxNU5tRjRVQjZQb2VMTC5Ya1phZkc5Sm9xVkVRWnZZcHFkTFNrZXliTU1lIiwidXBkYXRlZEF0IjoiMjAxOS0wNi0wM1QxNDozMjoyMS4zMDlaIiwiY3JlYXRlZEF0IjoiMjAxOS0wNi0wM1QxNDozMjoyMS4zMDlaIiwiX192IjowLCJpYXQiOjE1NTk2NTA3NzgsImV4cCI6MTU1OTY1NDM3OH0.qnfH_OHq2YqaKCRIbwtw788SQC51F8PJESRCf3Nlrak"
}
I then attempted to authorize with combinations of Bearer Token, OAuth2, prepending token with/without 'jwt ' etc, but nothing seems to get past the route guard on 'api/registry'.
/////////////////////////////////////////////
// server.ts - API auth routes
/////////////////////////////////////////////
server.use(
'/api/registry',
guard.check('user'),
require('./api/v1/registry')
)
server.use('/api/auth', require('./api/v1/auth'))
...
Result:
{
"name": "UnauthorizedError",
"message": "user object \"user\" was not found. Check your configuration.",
"code": "user_object_not_found",
"status": 403,
"inner": {
"message": "user object \"user\" was not found. Check your configuration."
}
}
The expected result would be that I can make an API call to '/api/registry' with the JWT token as a bearer token / OAuth2? and that should let me pass the route guard.
Thanks

How to validate Token from personal Microsoft Account?

I created a API that I try to protect with AAD.
It's already working for Accounts from my organization and Accounts from other organizations but not for personal Microsoft Accounts.
I already tryed different Endpoints but I think the common Endpoint should be the correct Endpoint if I want any Account to be able to sign in.
This is how my API Startup looks:
services.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = "https://login.microsoftonline.com/common";
o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
// Both App ID URI and client id are valid audiences in the access token
ValidAudiences = new List<string>
{
"APP ID",
},
ValidateIssuer = false,
};
});
And this is how I get the accesstoken at javascript:
var applicationConfig = { //Cloudlist API via TestApp
clientID: "APP ID",
authority: "https://login.microsoftonline.com/common",
graphScopes: ["https://hsde.onmicrosoft.com/APP ID/User"]
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID,
applicationConfig.authority, null, { storeAuthStateInCookie: true,
cacheLocation: "localStorage" });
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callAPI(accessToken);;
});
}, function (error) {
console.log(error);
});
When I sign in with a personal Microsoft Account and use the accessToken to call the API I get a 401 Unauthorized Error.
The response header says:
www-authenticate: Bearer error="invalid_token", error_description="The signature key was not found"
Is there anything I have to do differently when signing in with a personal Microsoft Account ?
First, get a token and try to decoded in jwt.io just to check if audience id is the same that you are using in you web api.

How to create Firebase token on server for use with unit tests?

I need to authenticate a Firebase user using node so I can test some server side methods. For each protected request, I verify the Firebase token using:
firebase.auth().verifyIdToken(firebaseAccessToken).then(function(decodedToken) {
// forward request
})
So in my test I created a token with a uid from my Firebase database
firebase.auth().createCustomToken(uid).then(function(token) {
//add to header for requests
})
Later I read that custom tokens are not verified by the verifyIdToken method, only client generated ones.
I've looked at this answer - server side verification of tokens in firebase
So I added databaseAuthVariableOverride to the init json
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: [dbURL],
databaseAuthVariableOverride: {
uid: [uid]
}
});
Still getting the output in my tests
Error: expected 200 "OK", got 401 "Unauthorized"
And the firebase error -
Error: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
So how do I emulate a user with my current setup?
Here's a Python script for generating Firebase ID tokens (not custom tokens).
python firebase_token_generator.py <UID>
There are probably easier ways to do this but you could call the Python script from Node.
You can generate a Firebase Id token from your custom token, then use that for verification. Eg:
const rp = require("request-promise");
// 'customToken' comes from FirebaseAdmin.auth().createCustomToken(uid)
function getIdTokenFromCustomToken(customToken) {
const url = `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=${API_KEY}`;
const data = {
token: customToken,
returnSecureToken: true
};
var options = {
method: "POST",
uri: url,
body: data,
json: true // Automatically stringifies the body to JSON
};
return rp(options)
// idToken is the firebase id token that can be used with verifyIdToken
.then(parsedBody => parsedBody.idToken)
.catch(function(err) {
// POST failed...
});
}

Resources