GCP Api Gateway with Firebase Auth - firebase-authentication

I'm trying to set up GCP Api Gateway for my Cloud Run hosted containers but I' getting some errors regarding Firebase Authentication.
In the api spec, I copied what is published in the api gateway guide
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/{{ project_id }}"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "{{ project_id }}"
and added the security entry in the endpoints path
security:
- firebase: []
When I make a request through the Api Gateway, some additional headers are added to the request that is proxied to the Cloud Run service, such as x-apigateway-api-userinfo, x-forwarded-authorization and authorization
Then, in the Cloud Run service, I have to use Firebase Admin sdk to verify the token passed and identify the user who is making the request. That's where the problem begins, when I try verifying any of this header's token, I get an error: "FirebaseAuthError: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token."
Am I doing anything wrong? How can I decode the auth token passed to the Api Gateway?

Checking the validity of the token is already done by the API Gateway.
x-apigateway-api-userinfo is not the JWT, it's a base64 encoded json containing the users information. Decode it and you should be golden.

Related

JWT token changes when passing through the GCP API gateway

I am sending a JWT token in api header. I designed this to pass through GCP api gateway and hit cloudrun service. But when passing through api gateway, the whole JWT token changes every time. There is no effect when I call the cloudrun directly without an api gateway. Any ideas about this?
You have several use cases
If you consider that your Cloud Run requires an authentication, but the access to API Gateway doesn't, the API Gateway is able to generate an identity token, based on the service account in its configuration, and add it to the request forwarded to Cloud Run
If you consider that your Cloud Run requires an authentication and you want to use API Gateway as authentication proxy (for instance, all the users that request the API gateway must be authorized by API gateway (by API key, by FirebaseAuth, by JWT token,...), but the users aren't directly granted on the Cloud RUn service, API Gateway is able to generate an identity token, based on the service account in its configuration, and add it to the request forwarded to Cloud Run
If you consider that your Cloud Run requires an authentication and API Gateway is simply a passthrough to centralise the APIs definition, you can set in your x-google-backend definition, the parameter disable_auth to true. That time, API Gateway won't generate an identity token and won't add it in the forwarded request. The identity token received in entry is forwarded to Cloud RUn (it must be a valid token for Cloud Run)
Note: when API Gateway generate an identity token, the initial authorization token is forwarded in a new header: X-Apigateway-Api-Userinfo

Authorizing access to Google Cloud Functions with ID token from Identity Platform

Intro
So I have read official docs Authenticating for invocation which is about helping developer testing and I got that working, but this approach requires a SA and a generated token. It seems the docs mix up "authentication" (proving identity) and "authorization" (giving access) which is not making it easier to get the whole picture.
I want to authorize Google Cloud Function with the user's ID token generated from Identity Platform. The official Firebase docs says:
"When a user or device signs in using Firebase Authentication, Firebase creates a corresponding ID token that uniquely identifies them and grants them access to several resources, such as Realtime Database and Cloud Storage. You can re-use that ID token to authenticate the Realtime Database REST API and make requests on behalf of that user."
My setup
I got the following artifacts to test function authorization with user:
A local React app with npm 'firebase' and a login form calling firebase.auth().signInWithEmailAndPassword.
firebase is initialized with config fields apiKey and authDomain.
An Express API deployed to Cloud Functions with default permissions, but I've provided the cloudbuild file with --allow-unauthenticated as an attempt to only focus on authorization.
A local Postman request setup calling the Express API with authorization type=Bearer Token and token set to the ID token received in the React app's onAuthStateChanged from user.getIdToken()
The Postman request responds with 401 Unauthorized. Notice it says Unauthorized, not 403 Forbidden.
Research
When reading up on the topic, I came across the following approaches to solve my problem:
Fetch the user id from the token and push it to a custom backend service which does admin.auth().setCustomUserClaims and then do the function request. GC should then hopefully know about the token's new claims.
Also about claims; generate a new token (based on current ID token?) and set claims.aud to the URL of the function. The ID token I'm using has claims.aud=projectname which I'm not sure what means.
Verify token in function code by using firebase admin. But the authorization of access is still not performed, so this approach seems to miss something.
What is required?
I suppose authentication is ok, Google Cloud should recognize the bearer token (?) but I've also read that there's no built-in functionality for this. Anyway, the authorization part is less clear to me when it comes to function requests on user level.
To summarize:
How should we authorize an ID token from Identity Platform to Google Cloud Functions? Could any of the three above-mentioned approaches be used?

difficulties making Cloud Run service the target of an Apps Script project--audience and scopes

I am unable to create an OpenID Connect identity token to send as bearer token for a cloud run request. From the apps script I cannot make a token using (ScriptApp.getIdentityToken()) which has an audience the Google front end will allow through. When I arrange for the script to send a token instead that I've made with gcloud print-identity-token--identical except for the audience--that invocation succeeds.
Note I believe this may the same issue as seen here: Securely calling a Google Cloud Function via a Google Apps Script.
Also google cloud authentication with bearer token via nodejs.
One workaround suggests restructuring the GCP/Apps Script projects. Others mostly use service accounts, and use service account keys. I believe it's possible using IAM and use of google auth for one to produce a usable SA identity token (short term service account credentials) but I can't demonstrate it.
I am working around this currently, but I'd like to understand the essential problem. I think it has something to do with the cloud run service's hosting project's Oauth consent screen, and the inability to add the associated web application client-id as a scope.
In the Cloud Run docs, there is a section about performing authenticated calls to Cloud Run from other services outside GCP. The process would be the following:
Self-sign a service account JWT with the target_audience claim set to the URL of the receiving service.
Exchange the self-signed JWT for a Google-signed ID token, which should have the aud claim set to the above URL.
Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the service.
Step 1 could be performed as described here while setting the aud claim to the URL of the receiving service. I believe ScriptApp.getIdentityToken() does not set the proper audience to the JWT
For step 2, I believe you should perform a POST request to https://oauth2.googleapis.com/token with the appropriate parameters grant_type and assertion. This is explained in the "Making the access token request" section here
The resulting token should be used in step 3
I just wrote an article on that topic and I provide an easy way based on the service account credential API. Let's have a look on it and we can discuss further if required.

Using JSON Web Token (JWT) for both Authentication and Authorization

I have a serverless web app using AWS lambda functions. For JSON Web Token(JWT), you can specify the payload. Is it possible to specify payload like
user: example#any.com
authorization-level: admin
and use the resulting token to not only authorize users but also authenticate users? (EX. users technically do not even have to log in to access API and get personal information because token has user email, which we verify and return related payload)
Or is this a major security breach?
It is perfectly fine to use JWT for both authentication and authorization, as long as you don't use NONE for signature algorithm. The signature guarantees that user has not tampered with the payload.

Oauth + SPA + API backend

I'm setting up a service which needs to authorize against an existing Gitlab as OAuth Provider.
The service is a SPA which gets served by a webpack dev server in dev mode and a nginx server in production mode.
I'm also setting up an external API which should handle the Database and make request to the given gitlab instance (for example pull repos).
My SPA is authorizing against the Gitlab OAuth with the implicit_grant flow and is getting an access token. Currently I pass the access_token after the redirect to my API backend and there I get the Gitlab userid and username via a request to the gitlab instance with the access_token. With these I generate a jwt and send it to the client (SPA) and save it there so I can authorize my API with this JWT.
How would I handle the initial access_token in my backend (cause I need the token to make gitlab calls)?
Currently I'm thinking about writing it to the user in the database and get the user everytime he makes a request (normal passport flow), so I also have the token. But what if the token gets invalid or expires?
Should I use an interceptor in the backend and if the token is invalid (gitlab would give me a 401) redirect the 401 to my client, let him get a new token and pass it back to the backend, generate a new JWT, send this again to the client and let him do the same request as original reuested(via interceptor, too)?
Or should I just redirect the 401 to my client, let him get a new token, let him post this token to for example /renewToken and save the token to the database and use the old JWT?
Hope someone can help me unserstand this flow.
The Credential Management API should be what your looking for on the client. That will retrieve the id and access tokens to that you can compare access tokens with your server/ap and then validate the id token.
Haven't seen a Git example but there are Google and Facebook examples.
You could let the user send the initial access token and your backend API will just act based on the initial access token. Seems to me that it is not necessary to produce another JWT token in this case.