Securing Backend APIs with GCP API Gateway and Auth0

Securing Backend APIs with GCP API Gateway and Auth0

You've had your billion dollar, unicorn start-up idea. Your mind is already racing with the architecture, frontend and backend code you'll need to make it a reality. Your main business logic will be served by the backend code. This will (usually) be accessed through RESP APIs that you've so lovingly and tenderly handcrafted with a sweet kiss on the forehead that only a loving mother could hope to replicate.

Now, time to secure your precious childre- I mean APIs, from potential harm...

Background

This guide will make use of Auth0 as an identity broker for a few reasons

  1. It's easy. Offload your identity and principal management to somebody else
  2. Auth0 provides rich libraries for handling Users, registrations, SSO, password resets and, most importantly for this post, accessTokens in a straightforward manner

We'll also be using Google Cloud's API Gateway as that's what I've experimented with and written a previous post on for handling CORS preflight checks - read about that here:

CORS in GCP API Gateway
If you’re like me, building an application with Backend APIs protected behind a Google Cloud API Gateway, you’ve probably encountered a common issue: The ability to handle CORS preflight requests from your FrontEnd! This is less of an issue with the self-administered ESP and ESPv2 offerings, but using the Managed

Pre-requisites

You'll need to create an Auth0 application suitable for your Front End. Once that's created, Navigate to Applications > Applications and select your newly created app. In the below screen, you'll see your Domain. Make a note of this!

Under Applications > API, you'll also need to create an API to represent your endpoints. Once done, make a note of the Identifier (Audience) that you will have provided during creation:

You should now have a note of your:

  • Auth0 Domain
  • Auth0 API Audience

You'll also need your Front End application to handle Auth0 login and send your Auth0 User's accessToken (this is important!) with each request. There is documentation on getting this for a number of frameworks, pick your poison.

Implementation

Once you've got the pre-requisites, the implementation is rather simple. Using the OpenAPI Spec YAML file, we can setup a security attribute and apply it to all of the endpoints we want to secure.

Here is a full example that we'll break down:

# openapi-spec.yaml
swagger: "2.0"
info:
  title: "Voqu VBaaS Backend API"
  description: "API for the Voicebot-as-a-Service platform"
  version: "1.0.0"
schemes:
  - "https"
produces:
  - "application/json"

host: "${api_gateway_managed_service}"
x-google-endpoints:
  - name: "${api_gateway_managed_service}"
    allowCors: True

securityDefinitions:
  auth0_jwt:
    authorizationUrl: ""
    flow: "implicit"
    type: "oauth2"
    x-google-issuer: "https://${auth0_domain}/"
    x-google-jwks_uri: "https://${auth0_domain}/.well-known/jwks.json"
    x-google-audiences: "${auth0_api_audience}"

# We are returning to the path-based approach, now with the validator exception.
paths:
  /hello:
    get:
      description: "A sample endpoint to test authentication."
      operationId: "hello"
      x-google-backend:
        address: "my-super-cool-url.highoncloud.co.uk/hello"
      security:
        - auth0_jwt: []
      responses:
        "200":
          description: "A successful response"

Remember, this is YAML so be careful with spacing. The above file uses 2-space indents.

The 2 aspects we're using to secure our endpoints in the above file are:

  • securityDefinitions - This block defines how the security will be checked, the JWT token issuer, URI and defined audiences
  • security - This block is defined as a parameter in each endpoint method that we wish to secure allowing modular application of security where required

The entire flow works as follows:

  1. The request reaches GCP API gateway with a header of Authorization: Bearer {token} as is the standard for Access Tokens
  2. GCP API Gateway sees that the endpoint being accessed (GET on /hello) has a security parameter so it expects a Token to be checked before allowing the request to pass through
  3. The GCP API gateway takes the provided token and uses the auth0 domain, JWKS URI and audiences to validate that the token belongs to a valid Auth0 user and contains the correct claims.
  4. If all checks succeed, the request is forwarded to the backend.
  5. If the checks fail i.e. the JWT token has expired or does not contain all of the correct, required claims, an error 401 Unauthorized is returned.

Deployment

For deployment, this guide uses terraform.

The below snippets creates everything you need. Ensure your API Spec YAML file is in your main terraform directory alongside the below code.

  • Enable the API Gateway Service
resource "google_project_service" "apigateway" {
  project            = var.project_id
  service            = "apigateway.googleapis.com"
  disable_on_destroy = false
}
  • Create the Gateway API Resource as a container for the YAML configuration
resource "google_api_gateway_api" "vbaas_api" {
  provider = google-beta # API Gateway resources are often updated in the beta provider
  project  = var.project_id
  api_id   = "vbaas-backend-api"
  display_name = "VBaaS Backend API"
  depends_on = [google_project_service.apigateway]
}
  • Store the OpenAPI Spec YAML file and pass in the required variables
resource "google_api_gateway_api_config" "vbaas_api_config" {
  provider     = google-beta
  project      = var.project_id
  api          = google_api_gateway_api.vbaas_api.api_id
  api_config_id_prefix = "vbaas-config-"
  display_name = "Voqu VBaaS Config v1.0.0"

  openapi_documents {
    document {
      path     = "openapi-spec.yaml"
      contents = base64encode(templatefile("${path.module}/openapi-spec.yaml", {
        auth0_domain          = var.auth0_domain
        auth0_api_audience    = var.auth0_api_audience
        api_gateway_managed_service  = var.api_gateway_managed_service
      }))
    }
  }

  # This lifecycle rule prevents downtime during updates by creating the new
  # config before destroying the old one.
  lifecycle {
    create_before_destroy = true
  }
}
  • Spin up the API Gateway
resource "google_api_gateway_gateway" "vbaas_gateway" {
  provider     = google-beta
  project      = var.project_id
  region       = var.region
  gateway_id   = "vbaas-gateway"
  api_config   = google_api_gateway_api_config.vbaas_api_config.id
  display_name = "Voqu VBaaS Gateway"

  depends_on = [google_api_gateway_api_config.vbaas_api_config]
}
  • <Optional> Add an output to easily find your API Gateway URL
output "api_gateway_url" {
  description = "The default hostname of the deployed API Gateway."
  value       = "https://${google_api_gateway_gateway.vbaas_gateway.default_hostname}"
}

Conclusion

Now, you can use Auth0 credentials to manage access to your backend APIs. The flexibility of this approach means that the security requirement can be added to any API endpoints and even specific HTTP Request methods for that endpoint, as and when required.