GCP API Gateway (Servlerless) 
Tags: googlecloud, apigateway, serverless, cloudrun
Published: 2020-10-20 02:10:17

Recently google has introduced a new API management service. Google Cloud API Gateway is a fully managed service that makes it easy for organizations/developers to create, publish, maintain, monitor and secure API's.  It acts as one of the "front door" _(ideally we want managed load balancer)_ for an application deployed on backend services like App Engine, Cloud Run and Cloud Functions, Compute Engine, or Google Kubernetes Engine.

In this article, we are going to deploy couple of endpoints from cloud run environment with front end proxy by API Gateway. We will have two API's to demonstrate public and secured (secured by firebase auth) by oauth2 Bearer Token.

**GCP Products**
* Cloud Run: HTTP backend services and accessible only through API Gateway 
* API Gateway: Frontend service to process the incoming request, and check the security if the services are secured.
* Firebase: Used to generate the custom token and exchange firebase custom token for google id token. 
* App Engine: HTTP endpoints for generating firebase custom token
 
We will breakdown the exercise in to multipart, first we will set up the backend services and integrated with API Gateway. Once that step is complete we will focus on the firebase/appengine parts to validate the security aspect.

###### Setup
* Git: Git is used to managing the binary
* GCP: You will need a google cloud project with billing enabled
* JDK11 environment


Create a GCP project and clone the repository [Link] (https://github.com/mohan-ganesh/apigateway-gcp.git)

Open the Google Cloud Shell or terminal clone the repository(https://github.com/mohan-ganesh/apigateway-gcp.git) and switch to cloud-run directory.

```
export PROJECT_ID=<YOUR-PROJECT-ID>
git clone https://github.com/mohan-ganesh/apigateway-gcp.git
cd apigateway-gcp/cloud-run
```

The cloud run application contains simple two services end points `healthcheck` and `identity`. `healthcheck` is a simple endpoint that just responds with > alive if the application is running. `identity` has small block of code that reads one of the header named *x-apigateway-api-userinfo*. 



```
 @RequestMapping (path="/identity", method= RequestMethod.GET,produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity identity(HttpServletRequest request)  {

        String authInfo = request.getHeader("x-apigateway-api-userinfo");
        byte data[] = Base64.getDecoder().decode(authInfo);
        return ResponseEntity.ok(new String(data));

    }
```

Build the application. This step may ask for the enablement of the Container Registry if it's not enabled.  

```
gcloud builds submit --tag gcr.io/$PROJECT_ID/apigateway-cloudrun
```
Enable the cloud run services, if it's not enabled.

```
gcloud services enable run.googleapis.com
```

Then, deploy the container in private mode to the cloud run. 
```
gcloud run deploy --image gcr.io/$PROJECT_ID/apigateway-cloudrun --platform managed --region us-central1 --no-allow-unauthenticated --memory=512Mi
```
Once the deploy completes, please make a note of the Cloud Run Service URL: https://<gcp-genrated-value>.run.app. 

Also, make a note of the cloud run service account. To get the cloud service account, execute the below command

```
gcloud iam service-accounts list
```
The account that has the following pattern *123456-compute@developer.gserviceaccount.com* is the cloud service account. We will need this information to use in open API definition. 

now, change the directory to the openapi-definition directory.

```
 cd ..
 cd openapi-definition
```
> Replace the `x-google-backend` address with cloud run service url. 
> Edit `x-google-issuer` url to add your project name.
> Edit `x-google-audiences` with your project name.  

 
```
paths:
  /healthcheck:
    get:
      summary: healthcheck
      operationId: healthcheck
      responses:
        '200':
          description: A successful response
          schema:
            type: string

  /identity:
    get:
      summary: print user identity
      operationId: headers
      security:
        - firebase: [ ]
      responses:
        '200':
          description: A successful response
          schema:
            type: string
```
 
Please note that in the above block of code *healthcheck* does not have any security, whereas *identity* path is secured with firebase security definition. 

At this point in time, you are ready to create the API Gateway *configs* and gateway's.


```
gcloud beta api-gateway api-configs create <open-api-config-name-v1> \
  --api=<cloudrun-sevice-name> --openapi-spec=openapi-spec.yaml \
  --project=$PROJECT_ID --backend-auth-service-account=<cloud-run-service-account>
```
 The above step creates the api-gateway config, now you are ready to create API *gateways*
```
gcloud beta api-gateway gateways create <open-api-gateway-v1> \
  --api=<cloudrun-sevice-name> --api-config=<open-api-config-name> \
  --location=us-central1 --project=$PROJECT_ID
```
If you need to update the config, replace create with an update.

The cloud run services are private at this point in time, we would like to grant "roles/run.invoker" permission to cloud run service account so that it can invoke the services.

```
gcloud run services add-iam-policy-binding <cloudrun-service-name> \
  --member "serviceAccount:<cloud-run-serviceaccount>" \
  --role "roles/run.invoker" \
  --platform managed \
  --region us-central1 \
  --project $PROJECT_ID
```

With these steps, you are almost ready to test the API service endpoints via GCP API Gateway.

To get the API Gateway generated url, please execute the command or navigate to API Gateway in GCP Console. 

```
gcloud beta api-gateway gateways describe <open-api-gateway-v1> \
  --location=us-central1 --project=$PROJECT_ID
```

You should see something like this. Click on the API's name and in the next section glance at the Configs and Gateways

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dmbyt48h8lixdop1ddpw.png)

You would see the information like below. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gvj2svo7ou8e0usceoz6.png)
The url displayed under Gateways is the API Gateway generated front-end url.

You can also get that url via gcloud command.

```
gcloud beta api-gateway gateways describe <open-api-gateway-config-name> \
  --location=us-central1 --project=$PROJECT_ID
```

**Test API**
Now you can send request's to your API
`
$ curl -X GET \
  https://open-api-gateway-<hash>.gateway.dev/healthcheck \
`
At the very first request, you may see a response like below.
> {
>    "code": 504,
>    "message": "upstream request timeout"
> } 

That is because, via api gateway, it request had tried to reach the backend, and response did not come in time. 
If you retry after a few seconds, you should see the response `alive`

This step's complete you have successfully able to invoke the API endpoint via GCP API Gateway. 

Now, if you do curl on the identity endpoint, you should see 
> {
>    "code": 401,
>    "message": "Jwt is missing"
> } 

At this point in time, this is a valid response for this endpoint.  

**Part 2**

At the beginning of the article, we said we will also demonstrate the API's secured by the firebase auth token.  As you can see when you make a request to 'identity' service, it's expecting the caller to pass a valid bearer token. It's time to generate one.

Head over to App Engine folder. This is also a simple spring-boot application that contains a couple of endpoints and we are interested in 'firebase/custom/token'. 

For more about firebase custom token, please refer [Link](https://firebase.google.com/docs/auth/admin/create-custom-tokens)


```
cd ..
cd app-engine-standard
mvn clean package
gcloud app deploy
```

With the above steps, the application gets deployed to App Engine Standard instance. 

Time to grant, a couple of permissions. 
below one for enabling IAM and the next one is for granting Account Token Creator permission for appspot service account.

Enable IAM services
```
 gcloud services enable iamcredentials.googleapis.com
```
Add TokenCreator role to app-engine service account. This step is needed in order to sign the JWT token that is going to be created. 

For more information, please read at [firebase documentation] (https://firebase.google.com/docs/auth/admin/create-custom-tokens#create_custom_tokens_using_the_firebase_admin_sdk). 

```
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:<replace-your-id>@appspot.gserviceaccount.com" \
    --role="roles/iam.serviceAccountTokenCreator"
```
If everything is fine, if you do test the custom token API you would get JWT. If you experience any error's you could also refer to the triage section at  [firebase troubleshooting documentation] (https://firebase.google.com/docs/auth/admin/create-custom-tokens#troubleshooting).

```
curl -X GET \
  'https://<app-engine-generated-value>.appspot.com/firebase/custom/token?userId=<unique-id>'
```
The response would be valid JWT, which is valid for one hour. 
This is still a valid token, but very short-lived and lesser scope. Most of the products such as endpoint and API gateway respect's OAuth bearer token. 

Now we will trade or exchange, the custom generated JWT token with google ID-Token. Before we do that, we need to get the Firebase project web api key. To get that value go to 
[Link](https://console.firebase.google.com) and add the created GCP project to enable the firebase. 

Then navigate to url (replace the project id with your project id) or click on Project Overview gear icon --> Project Settings. Under `Your Project` section you should have web-api-key there. Or visit the link by replacing your GCP project id. 

`https://console.firebase.google.com/u/0/project/<project_id>/settings/general`

Under the general section note down the *Web API Key*. This Web API key is being passed in the below api call to get the idToken. The reason for getting this Web API Key, this is how GCP knows the token that was generated belong to the respective project.  You can also able to find this information in GCP Console under the `Credentials` section.
 

```
curl -X POST \
  https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=<Firebase-Web-API-Token>' \
 -d '{
	"token":"<jwt-cutom-token>",
	"returnSecureToken":true
}'
```

You can also, get the same key via GCP console under `Credentials`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cwgupb8d9qyg4f1ktpnc.png)


if everything is fine, you will see a response like below

>
> {
>    "kind": "identitytoolkit#VerifyCustomTokenResponse",
>    "idToken": "long-base64encoded-string",
>    "refreshToken": "refreshToken",
>    "expiresIn": "3600",
>    "isNewUser": false
> }


from the response, grab the idToken, now you are ready to test the 'identity' api from api-gateway.

```
curl -X GET \
  https://open-api-gateway-<hash>.gateway.dev/identity \
  -H 'authorization: Bearer <idToken>' \
```
So, when we pass the Bearer token, API gateway does validate the token, if the token is valid then it would let the request to process to reach the backend. As a part of that step, API gateway adds two additional auth related headers.  Now you can have backend API's to just expect `x-apigateway-api-userinfo` if its present process the business logic. 



You should see JSON response like below, and that's the end of the article. 

> {
    "iss": "https://securetoken.google.com/<your-project-id>",
    "aud": "<your-project-id>",
    "auth_time": <token-generated-time>,
    "user_id": "<custom-token-unique-string>",
    "sub": "<custom-token-unique-string>",
    "iat": 1603075386,
    "exp": 1603078986,
    "firebase": {
        "identities": {},
        "sign_in_provider": "custom"
      }
}

With this implementation, the API can just focus on the business logic, and security is completely handled by the API Gateway layer.