Firebase REST auth when creating token with node.js admin sdk - node.js

I know this issue was asked a lot here, but I still cannot seem to find the exact answer that can solve my problem.
I wish to access Firebase using REST calls, by adding an access_token param.
The access_token is created using the Node.js Admin SDK, using the following code:
var admin = require("firebase-admin");
var serviceAccount = require("./pk.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://XXX.firebaseio.com"
});
var uid = "1234";
admin.auth().createCustomToken(uid)
.then(function(customToken) {
// Send token back to client
console.log("Token: "+customToken);
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
The problem is that if I take the token created from Node.js and use it my REST call, I get an Unauthorized request error.
I have read in some questions that people added the scope param when issuing the token, but have not found a way to do that with Node.js Admin SDK.
Google's docs are not so detailed with this issue. Any idea what I might try to resolve this one?

The token you are using to authenticate to the Firebase REST API is not the correct type of token. You are using a Firebase Auth custom token, which can only be used to authenticate one of the Firebase client SDKs via the signInWithCustomToken() method as explained in Sign in using custom tokens on clients.
In order to authenticate to the Firebase REST API, you have two options: Firebase ID tokens (for user-based access) or Google OAuth2 access tokens (for admin access).
Authenticate with Firebase ID Tokens
See Retrieve ID tokens on the client for an explanation of how to retrieve access tokens in the various Firebase client SDKs. You can also exchange a Firebase custom token for an ID token and refresh token pair via an undocumented REST API:
Endpoint: https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=<API_KEY>
Method: POST
Request Body: { "token": <CUSTOM_TOKEN>, "returnSecureToken": true }
<API_KEY> is the same API key you get from your Firebase Console that you use in the Firebase clients. <CUSTOM_TOKEN> is a Firebase custom token.
Since ID tokens expire after an hour, you will need to use the refresh token to refresh them via this other undocumented REST API:
Endpoint: https://securetoken.googleapis.com/v1/token?key=<API_KEY>
Method: POST
Request Body: { "refresh_token": <REFRESH_TOKEN>, "grant_type": "refresh_token" }
<API_KEY> is the same API key as before. <REFRESH_TOKEN> is the refresh token from the previous API call.
Once you have an ID token, you can pass that to the REST API via the auth query parameter to authenticate a request. The request respects Firebase Security Rules as if the end user logged into the client was making the request.
Authenticate with Google Access Tokens
To authenticate with a Google OAuth2 access token, the first thing you need to do is get one. See Retrieving an access token for an explanation of how to do this. It only currently includes a Java example, but this is possible in many languages, including Node.js. Once you have an ID token, you can pass that to the REST API via the access_token query parameter to authenticate a request. The request will be made with admin access, overriding all Firebase Security Rules and granting full read and write access.

Related

Can an open id connect id token be used to authenticate to an api

I am building a mern application.
the backend built using express exposes an api which users can create data and access the data they have created.
I want to allow users to sign in with google and get authorization to create and access the resources on this api which i control (not on google apis).
I keep coming across oauth 2 / open id connect articles stating that an Id token is for use by a client and a access token provided by a resource server should be used to get access to an api.
e.g. https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/
the reason stated for this is that the aud property on the id token wont be correct if used on the api.
I realise that some sources say: that if the spa and api are served from same server and have same client id and therefore audience I can use and id token to authenticate to the api, but I am looking to understand what I can do when this is not the case?
I feel using oauth2 for authorization is overkill for my app and I cant find any information about how to use open id connect to authenticate to my api.
Surely when you sign in to Auth0 authourization server using google it is just requesting an open id connect id token from google?
I am wondering if using Authorization Code Grant flow to receive an id token on the api server would allow me to authenticate a user to my api?
in this case would the api server be the client as far as open id connect is concerned and therefore the aud value would be correct?
I can generate an url to visit the google oauth server using the node googleapis library like so:
const { google } = require("googleapis");
const oauth2Client = new google.auth.OAuth2(
'clientid','clientsecret',
"http://localhost:3000/oauthcallback",//this is where the react app is served from
);
const calendar = google.calendar({ version: "v3", auth: oauth2Client });
const scopes = ["openid"];
const url = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: "offline",
// If you only need one scope you can pass it as a string
scope: scopes,
});
async function getUrl(req, res) {
console.log(url)
res.status(200).json({
url,
});
}
and use the following flow.
You are not supposed to access any API's using the ID-Token. First of all the life-time of the ID-token is very short, typically like 5 minutes.
You should always use the access-token to access API's and you can using the refresh token get new access-tokens. The ID-token you can only get one time and you use that to create the local user and local cookie session.
If you are using a SPA application, you should also consider using the BFF pattern, to avoid using any tokens in the SPA-Application
see The BFF Pattern (Backend for Frontend): An Introduction
I agree with one of the commenters that you should follow the principle of separation of concern and keep the authorization server as a separate service. Otherwise it will be a pin to debug and troubleshoot when it does not work.

How to validate Google access token locally using google oAuth libraries

I'm trying to use Google's APIs to modify data on my users' Google account through the use of an id_token for authentication and an access_token to actually use Google's APIs. I know I'm able to verify the authenticity of an id token like such:
import { OAuth2Client } from "google-auth-library";
const client = new OAuth2Client(GOOGLE_CLIENT_ID);
const ticket = await client.verifyIdToken({
token: idToken,
audience: GOOGLE_CLIENT_ID,
});
This verification happens locally on my device without needing to contact Google's servers each time a token needs to be verified.
I tried to figure out how to do the same for the access_token. The top answer on How can I verify a Google authentication API access token? post suggests that I should call an endpoint https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=accessToken to do the verification but that defeats my purpose of trying to do it locally.
What Google OAuth library/method can I use to verify an access token locally. Is it even possible?
Just to reiterate, I'm talking about the access_token, not the id_token.

Firebase ID token has incorrect "aud" (audience) claim when calling from Endpoint connected to Google Functions

I am using Google Endpoints as an API gateway which is running in a Google Run container service. The API path points to a Google Function (node js). The calls to the API gateway are from a web application (viz. browser).
One of the paths is: /login which authenticates a user in firebase using the firebase.auth().signInWithEmailAndPassword method. I get the token Id of the user and send it back in the response header (authentication bearer) back to the browser. This works as expected.
When other Requests are made (e.g /check) to the endpoint the token (in the header) is included. I wanted to check the validity of the token using the Firebase Admin method before processing any requests. The code in the Google Function that does this for one of the routes is as follows:
...
const decodeIdToken = async (req, res, next) => {
// Read the ID Token from the Authorization header.
const idToken = req.headers.authorization.split('Bearer ')[1];
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
req.decodedToken = decodedIdToken;
next();
return;
} catch (error) {
return res.status(403).json({
status: 'failure',
data: null,
error: error.message
});
}
};
// use decodeIdToken as middleware
app.post('/check', decodeIdToken, (req, res, next) => {
return res.json({
status: 'success',
data: req.decodedToken,
error: null
});
});
When I call (via Postman ) the routes by directly calling the Google Function trigger both the routes work. However, when I call the Google Endpoints which point to the Google Function I receive the following error while using the Firebase Admin object:
Firebase ID token has incorrect \"aud\" (audience) claim. Expected \"PROJECT-ID\" but got \"https://us-central1-PROJECT-ID.cloudfunctions.net/FUNCTION-NAME\". Make sure the ID token comes from the same Firebase project as the service account used to authenticate this SDK. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token
When setting the Firebase Admin object in NodeJs I tried the following:
const admin = require('firebase-admin');
admin.initializeApp();
as well as
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://PROJECT-ID.firebaseio.com"
});
Use the X-Apigateway-Api-Userinfo Header
The header's value is the base64 encoded payload of the JWT. There's no need to reverify as API Gateway already verified the token for you and has made the contents available for your use.
Example for Node.js devs:
Buffer.from(req.header("x-apigateway-api-userinfo"), "base64").toString();
If for whatever reason you do need access to the original JWT, it is available in the X-Forwared-Authorization header.
Unnecessary Extra Credit:
To explain the error, the reason you are getting the wrong Audience claim is because the JWT you are trying to verify is a different JWT generated by API Gateway. The original Authorization Header has been replaced with this JWT. Why? It is telling Cloud Functions "Hey Cloud Function, it's me API Gateway that's calling you and here's a signed JWT to prove it". Hence API Gateway's audience ends up being the Cloud Function resource url whereas Firebase's audience is the Project the Firebase sits in.
Just another example of weird inconveniences due to Google's implementation if you ask me; they could have definitely left the Auth header untouched and had API Gateway use a different header, but beggars can't be choosers. 🤷‍♂️
Reference API Gateway Documentation:
Receiving authenticated results in your API
API Gateway usually forwards all headers it receives. However, it overrides the original Authorization header when the backend address
is specified by x-google-backend in the API config.
API Gateway will send the authentication result in the X-Apigateway-Api-Userinfo to the backend API. It is recommended to use
this header instead of the original Authorization header. This header
is base64url encoded and contains the JWT payload.
The following worked does not work (see comment below):
In the openapi-functions.yaml add the security defintion as recommended by the docs
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
# Replace YOUR-PROJECT-ID with your project ID
x-google-issuer: "https://securetoken.google.com/YOUR-PROJECT-ID"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "YOUR-PROJECT-ID"
Then, against the path (/check in my case), add the security section as below:
/check:
post:
...
x-google-backend:
....
....
security:
- firebase: []
....
Refer to: https://cloud.google.com/endpoints/docs/openapi/authenticating-users-firebase
There isn't problem with your admin-sdk settings, it's the idToken which is actually a jwt token retured as idToken while sign in using firebase.
Your problem is you are trying to use the JWT token returned as idToken by one of the auth() functions like firebase.auth().signInWithEmailAndPassword These do return a JWT token, however the auth claims will likely be wrong and won't pass verification by verifyIdToken. Firebase tech support confirmed this.
You have to use the firebase.auth().currentUser.getToken() function. That token will pass verification.
const idToken=await firebase.auth().currentUser.getToken()

authentication header vs query parameter in google cloud endpoints

I have tried everything, yet I cannot access my API using google cloud endpoints using a Authentication:Bearer header. According to Cloud Endpoints Docs:
When you send a request using an authentication token, for security reasons, we recommend that you put the token in the Authorization:Bearer header.
it also says:
If you cannot use the header when sending the request, you can put the authentication token in a query parameter called access_token.
I can perfectly access the API using access_token=" +idToken in my URL. However, when I try to send an HTTP request with the Authentication header like this:
const url =
"https://<PROJECTNAME>.appspot.com/getbalance";
axios
.get(url,{headers:{'Authentication':'Bearer '+idToken}})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
I get this error:
JWT validation failed: Missing or invalid credentials
Is sending the token in a query parameter as safe as sending it in the header?
Your code example shows you setting an Authentication header, not an Authorization header. You should not typically use a query parameter as it will likely get logged in Cloud Console.
When using "Authorization: Bearer ", you would need to use an access token obtained through OAuth 2.0 authentication.
This can be illustrated if you use the Oauth Playground agains any of the Google APIs.
Keep in mind that if you want to access your Firebase database using the Oauth Playground, you would need to configure the client ID and client Secret of your Firebase project on the gear icon at the top right of the playground screen.
Also make sure to use these scopes:
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/firebase.database
After completing all the steps, you will be able to make a REST request using the authorization header with the obtained access token.

Firebase Admin Token is being generated with invalid signature

I have created an App using the Parse server, and have now decided to implement some aspects of firebase into my app. Im trying to accomplish this by doing the signInWithCustomToken method in swift on ios. Im calling a rest service to generate a firebase token, and then signing in with that token returned upon valid sign in on my current auth system. However, the token being generated appears to have an invalid signature when pasting it into jwt.io. The environment that im using is node based (inside the parse server). It seems very simple and i have followed the firebase instrucutions/poured over questions on this over the last few days and im really not sure what im doing wrong! Heres my rest service on how i generate the token, i initialize the server with this method:
Parse.Cloud.define("initServer", function(request, response){
var admin = require('firebase-admin');
var serviceAccount = require('/home/bitnami/apps/parse/htdocs/firebase/serviceAccountKey.json');
console.log(serviceAccount);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://urabo-acb7a.firebaseio.com'
});
response.success("Server Init OK");
});
this is the post method i call to generate the token from firebase-admin sdk:
Parse.Cloud.define("generateFirebaseToken", function(request, response) {
var admin = require('firebase-admin');
admin.auth().createCustomToken(request.params.uid)
.then(function(customToken) {
// Send token back to client
response.success(customToken);
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
});
And of course I went into the firebase console and generated the private key, and then scp'd it over to my server. Im not really sure why this is not working, it generates a token it just doesnt appear to be valid or linked to my account. Am i missing some weird encoding issue or something with the token? Does anyone have insight on this?? Thanks so much!
—The main question is are your users signing in to the REST service through your app and then you are also trying to re-authenticate them again with the token generated in your system?
—If they will be accessing further REST functions once authenticated, then why not authenticate them successfully when 'a' token is returned?
—Usually token usage or handling is restricted by the API providers. Another option is instead of involving user auth directly with the API service, have a separate auth system — the usual SignIn process and then make API calls based on the requested API feature. That way your app is interacting with the APIs and users remain at the front end.

Resources