Skip to main content

Centralized Authorization on GCP: Casbin, Envoy, and API Gateway

· 8 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

Centralized Authorization on GCP: Casbin, Envoy, and API Gateway

Implementing authorization within a web application, especially in a microservices environment, can quickly lead to duplicated or inconsistent security logic. By leveraging the External Authorization Pattern using Envoy Proxy and Casbin, you can decouple security checks from your Flask application, making your architecture cleaner, more secure, and scalable.

This strategy is particularly effective on Google Cloud Platform (GCP) when using services like API Gateway or Cloud Endpoints as the entry point, and Firestore as the persistent policy backend.


The Cloud-Native Authorization Architecture

The recommended architecture establishes a clear pipeline where authorization is strictly enforced before a request ever reaches the application code.

The Request Flow (API Gateway to Flask)

  1. Client $\rightarrow$ API Gateway / Cloud Endpoints: The request first hits the Google API Gateway. This layer is ideal for handling initial authentication (e.g., validating a JWT, basic rate limiting) and routing.
  2. API Gateway $\rightarrow$ Envoy Proxy: The authenticated request is routed to your Flask application's container, which is fronted by an Envoy proxy (typically running as a sidecar in a GKE or Cloud Run environment).
  3. Envoy $\rightarrow$ Casbin Authorization Service (envoy-authz): Envoy intercepts the request and forwards all relevant headers, method, and path data to a dedicated, lightweight service running the Casbin logic. This service is often called the envoy-authz service.
  4. Casbin Service $\rightarrow$ Firestore Policy: The Casbin service maintains an in-memory instance of the Casbin enforcer. It uses a Firestore Adapter to load the current policy and grouping rules (the model, policy, and user/role assignments) from your database.
  5. Authorization Decision: Casbin enforces the policy (e.g., using the RBAC model) based on the user's role, the resource (/api/v1/data), and the action (GET).
  6. Envoy Action:
    • Allowed: Envoy forwards the request to the Flask application. It can optionally inject the user's ID and discovered roles into the request headers.
    • Denied: Envoy immediately terminates the request and returns a 403 Forbidden response to the client.

Why This Pattern is Superior for Flask/GCP

This centralized approach offers distinct advantages over implementing Casbin directly within your Flask application using a local middleware:

BenefitStandard Middleware (In-App)External Authorization (Envoy)
DecouplingAuthorization logic is intertwined with the Flask service, making updates complex.Flask app focuses only on business logic; authorization is an external service.
PerformanceAuthorization checks run inside the Python process, consuming Python resources even if denied.Request is stopped at the proxy layer (Envoy), saving CPU cycles and memory usage for the Flask app.
ConsistencyRequires careful integration into every service and language framework.Uniformly enforced across the entire service mesh, regardless of the language (Python, Go, Node.js).
Policy BackendEach Flask instance may contend with Firestore for policy loading.Policy is loaded and cached once by the dedicated Casbin service, minimizing Firestore access.

Implementation Details with Casbin and Firestore

To make this architecture work with your chosen stack, the envoy-authz service needs to be optimized for low latency and policy persistence.

1 The Python Casbin Authorization Service

You would create a small, fast microservice (e.g., using a lightweight framework like FastAPI or a custom gRPC server) using the python-casbin library.

Example: Core Casbin Enforcement Logic

from casbin import Enforcer
from casbin_adapter_firestore import FirestoreAdapter # Hypothetical adapter

# 1. Initialize the Enforcer
# This happens once when the service starts
adapter = FirestoreAdapter()
enforcer = Enforcer('model.conf', adapter)

def authorize_request(user_id, path, method):
"""
Checks user permission against the loaded policy.
"""
# Look up user's roles (g rules) and permissions (p rules)
if enforcer.enforce(user_id, path, method):
return True # Allowed
else:
return False # Denied

# In your gRPC/HTTP endpoint:
# is_allowed = authorize_request("alice", "/api/v1/data", "GET")
# If is_allowed is False, the service returns a 403 status to Envoy.

Annotation: The enforcer.enforce() method handles the user-to-role lookup (g rules) and the role-to-permission check (p and g2 rules) instantly, using the policy data loaded from Firestore.

2 Policy Persistence with Firestore

Since Casbin requires policies to be loaded into memory, you need a stable and reliable way to sync with your Firestore database.

  • Firestore Adapter: A specific Casbin Adapter is necessary to translate the rows in your Firestore collection(s) into the Casbin policy structure (p, g, g2 rules). You would use the adapter to load the policy when the Casbin service starts and to automatically watch for changes.
  • Change Watching: For high performance, the Casbin service should use a watcher mechanism (or Firestore's real-time listeners) to reload the policy only when a change occurs in the database, avoiding a full policy reload on every request [3].

By implementing authorization at the Envoy level, you ensure your Flask application receives only clean, authorized traffic, simplifying your code and hardening your security posture.


Sources and Further Reading

  1. Envoy Documentation - External Authorization
  2. API Gateway Pattern (Microsoft Docs):
  3. Casbin Documentation - Watcher (Policy Updates)
  4. Casbin Documentation - Adapters (Policy Persistence)