LAB45_GCP_API Security: Validating Identity Platform JWTs at the Gateway

API Security: Validating Identity Platform JWTs at the Gateway

☁ GCP 🔥 Intermediate API Gateway Identity Platform JWT Security

Scenario

Your mobile app allows users to log in (using email or Google OAuth). This process leverages GCP Identity Platform (formerly Firebase Auth), which issues a JWT (JSON Web Token) to the user's phone. When the phone wants to read data from your API, it attaches the JWT. You will configure an API Gateway OpenAPI specification to automatically sniff the token, validate the cryptographic signature against Google's public keys, and reject unauthenticated requests before they ever reach your backend Cloud Run service.

Your Objectives
  • Update a openapi.yaml file to include the securityDefinitions block targeting Firebase/Identity Platform.
  • Deploy a Cloud Run backend (unauthenticated, strictly relying on API Gateway to block abuse).
  • Deploy the google_api_gateway_api and google_api_gateway_api_config containing the security configuration.

Additional Context

API Gateway Security Models: GCP API Gateway can perform several types of security validation before routing traffic: API Keys (`x-api-key`), Service Account signatures, or JWTs. Validating JWTs at the Gateway removes the burden of writing JWT parsing/verification libraries into your application code.

Firebase/Identity Provider URI: The issuer URL for Identity Platform JWTs is rigidly defined as `https://securetoken.google.com/[YOUR_PROJECT_ID]`.

openapi.yaml.tpl

swagger: "2.0"
info:
  title: "Secure User Data API"
  version: "1.0.0"
paths:
  /profile:
    get:
      summary: "Get User Profile"
      operationId: "getProfile"
      # Route request to backend
      x-google-backend:
        address: "${backend_url}"
      # Enforce the JWT security definition defined below!
      security:
        - firebase: []
      responses:
        200:
          description: "Success"

# Define How the Gateway Validates the Token
securityDefinitions:
  firebase:
    # Must be of type oauth2 or apiKey in swagger, but x-google-issuer makes it JWT
    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/[email protected]"
    x-google-audiences: "${project_id}"

main.tf

provider "google" {
  project = var.project_id
  region  = "us-central1"
}

provider "google-beta" {
  project = var.project_id
  region  = "us-central1"
}

# 1. The Backend (Blindly trusts the Gateway for authentication)
resource "google_cloud_run_v2_service" "backend" {
  name     = "user-profile-api"
  location = "us-central1"
  template {
    containers { image = "us-docker.pkg.dev/cloudrun/container/hello" }
  }
}

# Allow external traffic (Gateway) to access the backend unauthenticated
resource "google_cloud_run_service_iam_member" "allUsers" {
  location = google_cloud_run_v2_service.backend.location
  service  = google_cloud_run_v2_service.backend.name
  role     = "roles/run.invoker"
  member   = "allUsers"
}

# 2. Add API API Gateway
resource "google_api_gateway_api" "secure_api" {
  provider = google-beta
  api_id   = "jwt-secured-api"
}

resource "google_api_gateway_api_config" "secure_config" {
  provider      = google-beta
  api           = google_api_gateway_api.secure_api.api_id
  api_config_id = "jwt-cfg-v1"

  openapi_documents {
    document {
      path     = "openapi.yaml"
      contents = base64encode(templatefile("openapi.yaml.tpl", {
        backend_url = google_cloud_run_v2_service.backend.uri
        project_id  = var.project_id
      }))
    }
  }
  
  # Identity Platform validation sometimes breaks if the backend hasn't settled
  lifecycle { create_before_destroy = true }
}

resource "google_api_gateway_gateway" "api_gw" {
  provider   = google-beta
  api_config = google_api_gateway_api_config.secure_config.id
  gateway_id = "jwt-gw"
  region     = "us-central1"
}

Workflow Commands

terraform init
terraform apply -var="project_id=my-gcp-project-123"

# Attempt standard access (Will return 401 Unauthorized via Gateway)
curl https://$(terraform output -raw gateway_url)/profile

# Attempt authenticated access (Will return 200 OK from Cloud Run)
curl -H "Authorization: Bearer " \
  https://$(terraform output -raw gateway_url)/profile

terraform destroy -var="project_id=my-gcp-project-123"