JWT verification fails with ESPv2 with Firebase authentication - google-cloud-platform

I was building authenticated Cloud functions usingCloud functions with ESPV2 and Firebase authentication and API Management. Once I got the JWT token from firebase after authentication, I tried curl to the link with the token in Authorization as Bearer. I got 'JWT verification fails' when I tried in postman. I got 'Bad Request' when I tried it from my client application. Other than the setup mentioned in the links, do I need to do anything extra before I make the request?
Update with more details as requested
swagger: "2.0"
info:
title: My API Endpoints
description: My API Endpoints
version: 1.0.0
host: myapi-abcdefg.a.run.app
schemes:
- https
produces:
- application/json
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/fan-demand"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "my-google-project-id"
paths:
/getevents:
get:
summary: Get Events
operationId: getevents
x-google-backend:
address: https://us-central1-my-google-project-id.cloudfunctions.net/getevents
protocol: h2
security:
- firebase: []
responses:
"200":
description: A successful response
schema:
type: string
"403":
description: Failed to authenticate
After deploying this service, I get the id token from Firebase using the getIdToken() method in the Firebase Dart SDK. The JWT token is in the Header.payload.tail format. Then I added the token in the Authorization header with Bearer + id token and I get the following response.
Update:
I tried the new API Gateway product using https://cloud.google.com/api-gateway/docs/authenticating-users-firebase instead of ESP.
My configuration:
swagger: "2.0"
info:
title: My API Endpoints
description: My API Endpoints
version: 1.0.0
schemes:
- https
produces:
- application/json
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/my-project"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "my-project"
paths:
/getevents:
get:
summary: Get Events
operationId: getevents
x-google-backend:
address: https://us-central1-my-project.cloudfunctions.net/getevents
security:
- firebase: []
responses:
"200":
description: A successful response
schema:
type: string
"403":
description: Failed to authenticate
Client Side Code:
Client side is developed in dart and user here is a firebase auth object from https://pub.dev/documentation/firebase_auth/latest/firebase_auth/User/getIdToken.html
user.getIdToken().then((token) async {
final response = await http.get(
Uri.parse(
'https://mygateway/getevents'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
});
print('Token : ${token}');
print(response.body);
});
I got the response
403 Forbidden - Your client does not have permission to get URL

Without ESP
Cloud functions need to be deployed publicly (with allUsers) to be able to use firebase authentication.
Be careful:
Unlike Google Sign-in above, your function is doing the authentication;
therefore, you will be billed for unauthenticated requests since the function must do work to validate the token.
Link to relevant documentation
With ESP
If you want to use cloud functions with ESPv2 in front of it, you need to create a specific IAM for your ESP to be able to trigger privately your cloud functions.
To provide API management for Cloud Functions, you deploy the prebuilt ESPv2 container to Cloud Run.
You then secure your functions by using Cloud Functions IAM so that ESPv2 can invoke them.
Link to relevant documentation

Related

Google Cloud API Gateway can't invoke Cloud Run service while using firebase auth

I am using API Gateway with firebase JWT authorisation (so that users can use google sign in) that is forwarding the requests to cloud run services and one cloud function service.
Here is how my API Gateway config looks like:
swagger: '2.0'
info:
version: '1.0.0'
title: 'BFF'
description: Backend For Frontend
schemes:
- https
security:
- firebase: []
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/${PROJECT}"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: ${PROJECT}
paths:
/test/auth:
post:
operationId: testAuth
summary: Test auth
produces:
- application/json
x-google-backend:
address: https://${REGION}-${PROJECT}.cloudfunctions.net/auth-test
responses:
'200':
description: 'Response returns user related data from JWT'
/record/new:
post:
operationId: crateRecord
summary: Create new record
x-google-backend:
address: ${RUN_SERVICE_URL}:${RUN_SERVICE_PORT}/new
produces:
- application/json
parameters:
- in: body
name: data
description: Data for new record
schema:
$ref: '#/definitions/Record'
responses:
'200':
description: New record data
schema:
$ref: '#/definitions/Record'
'400':
description: Invalid input data
The problem is that API Gateway for some reason can't invoke cloud run service but it can invoke cloud functions:
┍ Client is passing authorization token in header
|
| ┍ Auth is successful and request is forwarded to cloud run
| |
| | ┍ 401 unauthorized to invoke cloud run
| | |
↓ ↓ ↓
Client -----------> API Gateway -----X-----> Cloud run service
API Gateway service account has these relevant roles: roles/cloudfunctions.invoker, roles/run.invoker and roles/iam.serviceAccountUser
Run service also has IAM binding for gateway service account with the role roles/run.invoker
When I use /test/auth route I can see that firebase auth is working as expected and I can trigger cloud functions without any problem and in response cloud function returns data from x-apigateway-api-userinfo as expected. But when i make request with same authorization token to run service route /record/new I get:
www-authenticate: Bearer error="invalid_token" error_description="The access token could not be verified"
401 Unauthorized
Your client does not have permission to the requested URL /new.
I am running out of ideas on what might be the problem, any advice would be helpful.
With Cloud Functions, the identity token created contains automatically the correct audience. It's not the case when you invoke Cloud Run, you have to explicitly mention the Cloud Run audience
/record/new:
post:
operationId: crateRecord
summary: Create new record
x-google-backend:
address: ${RUN_SERVICE_URL}:${RUN_SERVICE_PORT}/new
jwt_audience: ${RUN_SERVICE_URL}
Have a try, it should work now.

Configure Google Cloud API Gateway for optional Authenticated with JWT, and unauthenticated?

I am trying to set up a GraqphQL Server, on Cloud Functions, and I want to set up an API Gateway to handle authentication with auth0 and jwt....
I have it working from the tutorial, the problem is it always requires a JWT token, where as I want some GraphQL queries to be available publicly, and if the user signs in they get more access...
From my understanding the way you do this is by using two authentications in the security settings, where one is the JWT and one is empty, however the API Gateway seems to always want the JWT token... Here is my open API spec, maybe someone has an idea?
swagger: '2.0'
info:
title: <redacted>-graphql-api
description: Basic GraphQL Open APISchema
version: 1.0.0
schemes:
- https
produces:
- application/json
securityDefinitions:
auth0_jwk:
authorizationUrl: "<redacted>"
flow: "implicit"
type: "oauth2"
# Replace YOUR-ACCOUNT-NAME with your Auth0 account name.
x-google-issuer: "<redacted>"
x-google-jwks_uri: "<redacted>"
# Optional. Replace YOUR-CLIENT-ID with your client ID
x-google-audiences: "<redacted>"
paths:
/:
post:
summary: GraphQL endpoint
operationId: gql
x-google-backend:
address: <redacted> # App URL/endpoint
responses:
'200':
description: A successful response
schema:
type: object
security:
- {}
- auth0_jwk: []
get:
summary: GraphQL Playground
operationId: playground
x-google-backend:
address: <redacted> # App URL/endpoint
responses:
'200':
description: A successful response
schema:
type: string

GCP API Gateway JWT always returning 403

I'm using gcp api gateway for JWT authentication. after generating a token from my auth service and then putting it in postman I always receive this response no matter what I put in the 'aud' part of the token:
Here is my open api file:
# openapi2-run.yaml
swagger: '2.0'
info:
title: my-gateway-id
description: Sample API on API Gateway with a Cloud Run backend
version: 1.0.0
schemes:
- https
produces:
- application/json
x-google-backend:
address: https://my-cloud-run.a.run.app
jwt_audience: https://my-cloud-run.a.run.app
securityDefinitions:
jwt_auth:
authorizationUrl: ''
flow: 'implicit'
type: 'oauth2'
x-google-issuer: 'id-admin#my-project.iam.gserviceaccount.com'
x-google-jwks_uri: 'https://www.googleapis.com/service_accounts/v1/metadata/x509/id-admin#my-project.iam.gserviceaccount.com'
paths:
/:
post:
security:
- jwt_auth: []
summary: GraphQL endpoint
operationId: gql
responses:
'200':
description: A successful response
schema:
type: object
I've looked over and over the docs and can't see what's going on? thanks in advance.
You get 403 because the aud on the JWT token you've generated is not found on securityDefinitions of your API config.
To allow additional client IDs to access the backend service, you can specify the allowed client IDs in the x-google-audiences field by using comma-separated values. API Gateway then accepts the JWTs with any of the specified client IDs in the aud claim.
Go here and paste your token to see your JWT "aud" claim. If you generated the ID token using gcloud auth, the aud will most likely be a Client ID like 1234567890.apps.googleusercontent.com. But if you generated the token using your own service, then it would depend on what you've specified as a target audience.
To solve the problem, add x-google-audiences field on the securityDefinitions section and the value should match with your JWT "aud" claim.
Assuming that the aud on your JWT token is a Cloud Run service endpoint, then your API config should look like this. Feel free to check the documentation as additional reference:
x-google-backend:
address: https://my-cloud-run.a.run.app
securityDefinitions:
jwt_auth:
authorizationUrl: ''
flow: 'implicit'
type: 'oauth2'
x-google-issuer: 'id-admin#my-project.iam.gserviceaccount.com'
x-google-jwks_uri: 'https://www.googleapis.com/service_accounts/v1/metadata/x509/id-admin#my-project.iam.gserviceaccount.com'
x-google-audiences: 'https://my-cloud-run.a.run.app'
If you have multiple audiences, then it should be a single string separated by a comma. Spaces aren't allowed between the audiences.

API Gateway not returning Response

I was trying GCP API Gateway using firebase authentication. I can see my request has been processed from the logs and completed with response code 200. However, I am not getting the response back to my client. I am getting the response when I call the function directly. Am I missing something ?
API Config
swagger: "2.0"
info:
title: API Endpoints
description: API Endpoints
version: 1.0.1
schemes:
- https
produces:
- application/json
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/my-project"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "my-project"
paths:
/hello:
get:
summary: Test link
operationId: hello
x-google-backend:
address: https://us-central1-my-project.cloudfunctions.net/hello
security:
- firebase: []
responses:
"200":
description: A successful response
schema:
type: string
"403":
description: Failed to authenticate
Function
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(message);
};
Logs
Additional Query
Initially, I had my functions private and was getting 403. It gave me 200 once I added allUsers with Cloud Functions Invoker to the permissions to the function I am trying to invoke. So I am a bit confused here. Part of the reason I am using API gateway with firebase auth is to protect it against unauthorised calls. And for firebase auth to work, I had to add allUsers, making it public. My understanding was that the API gateway alone would be public while all the backend services that it invokes would be private. In this case, the function can be directly invoked by anyone, rendering API Gateway useless. How can I setup the backend to private and only respond to authenticated calls through API gateway ?
Additional Logs
{
insertId: "8c13b49c-2752-4216-8188-d445f4724ef14850908905639612439#a1"
jsonPayload: {
api_key_state: "NOT CHECKED"
api_method: "1.myapi.GenerateRTCToken"
api_name: "1.myapi"
api_version: "1.0.1"
client_ip: "999.999.999.999"
http_method: "GET"
http_response_code: 200
location: "us-central1"
log_message: "1.myapi.GenerateRTCToken is called"
producer_project_id: "myproject"
request_latency_in_ms: 161
request_size_in_bytes: 4020
response_size_in_bytes: 579
service_agent: "ESPv2/2.17.0"
service_config_id: "myapi-config"
timestamp: 1606313914.9804168
url: "/generateRTCToken"
}
logName: "projects/myproject/logs/myapi%2Fendpoints_log"
receiveTimestamp: "2020-11-25T14:18:36.521292489Z"
resource: {
labels: {
location: "us-central1"
method: "1.myapi.GenerateRTCToken"
project_id: "myproject"
service: "myapi"
version: "1.0.1"
}
type: "api"
}
severity: "INFO"
timestamp: "2020-11-25T14:18:34.980416865Z"
}
To call the Google Cloud Function from the Api-Gateway without making it public you can grant the service account used by the API-Gateway the role CloudFunctions Invoker
On Creating an API config (4) : you set a service Account, take note of this service account.
Once you have identified the service account you can grant it the Cloud Funtions Invoker role to the service account. For these you can follow these steps:
Go to IAM&Admin
Look for the service account, and click on the pencil next to it( If you don't see the service account click on Add and type the service account email)
For role Select Cloud Functions Invoker
Click on Save
This will grant the service account the permission to call the functions without having them public.
I have an similar issue with API Gateway throwing the same response validating a token against keycloak.
The issue was with the JWT, it was too long, we remove unused roles from the user and work perfectly.
Hope it helps.
Take a look at this document where it is explained in detail how to use Firebase to authenticate in API Gateway.
In general there are two conditions that we need to keep on mind in order to configure these services:
When your client application sends an HTTP request, the authorization header in the request must contain the following JWT claims:
iss (issuer)
sub (subject)
aud (audience)
iat (issued at)
exp (expiration time)
To support Firebase authentication, add the following to the security definition in your API config:
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"

Google ESP optional auth

I am using Google ESP and on an endpoint I have authorisation with Auth0 like so:
securityDefinitions:
auth0_jwk:
authorizationUrl: "AUTH_URL"
flow: "implicit"
type: "oauth2"
x-google-issuer: "AUTHO_URL"
x-google-jwks_uri: "AUTH0_JWKS_URL"
x-google-audiences: "AUTH0_AUDIENCE"
paths:
/pluginviewservice/v1:
post:
summary: Submits a page to be analysed with auth.
operationId: pluginviewservicev1
x-google-backend:
address: APP_ENGINE_URL
path_translation: CONSTANT_ADDRESS
responses:
'200':
description: A successful response
schema:
type: string
x-security:
- {}
- auth0_jwk:
audiences:
- "AUTH0_AUDIENCE"
However what I want is, when the auth0 access token is available it populates "X-Endpoint-API-UserInfo", and when it isn't it lets it through anyway. The endpoint is designed to be accessible both to authorised and unauthorised users. I have tried adding a blank "x-security" as seen above, but that does not seem to work.