Skip to main content

Firebase RBAC Integration

Overview

This document describes the Firebase Authentication and Role-Based Access Control (RBAC) integration in our system. The architecture uses a custom header approach to bypass ESPv2 JWT processing while maintaining secure Firebase token validation.

Architecture

Authentication Flow

Frontend (Angular) → ESPv2 Proxy → Java Backend
OR
Frontend (Angular) → Direct gRPC → Java Backend

ESPv2 Proxy Mode (Recommended):

  1. Frontend: Sends Firebase token in both Authorization and x-original-authorization headers
  2. ESPv2: Processes Authorization header, passes through x-original-authorization header untouched
  3. Backend: Validates original Firebase token from x-original-authorization header

Direct gRPC Mode (Testing):

  1. Frontend: Sends Firebase token in Authorization header
  2. Backend: Validates Firebase token from Authorization header

Security Model

  • Authentication: Firebase ID tokens validated by Firebase Admin SDK in backend
  • Authorization: Custom RBAC system using Firebase custom claims
  • ESPv2 Role: Reverse proxy with Firebase authentication + CORS support
  • Custom Header: x-original-authorization bypasses ESPv2 JWT processing, preserving original audience
  • No Audience Mismatch: Original Firebase tokens maintain correct audience for backend validation

Configuration

Frontend Configuration (Angular AuthInterceptor)

The frontend sends Firebase tokens in dual headers for maximum compatibility:

// Set both headers for maximum compatibility:
// 1. Standard Authorization header (for ESPv2 authentication)
metadata['Authorization'] = `Bearer ${token}`;

// 2. Custom header with original token (for backend Firebase validation)
// This bypasses ESPv2's JWT processing and preserves original audience
metadata['x-original-authorization'] = `Bearer ${token}`;

ESPv2 Configuration (env/dev/gcp/cloud-run/endpoints/api_config.yaml)

ESPv2 is configured with Firebase authentication and allows the custom header through:

authentication:
providers:
- id: firebase
jwks_uri: https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com
issuer: https://securetoken.google.com/construction-code-expert-dev
audiences: "construction-code-expert-dev"
rules:
# Enable Firebase authentication for all services
- selector: "*"
requirements:
- provider_id: firebase

ESPv2 CORS Configuration (env/common/gcp/cloud-run/endpoints/vars.yaml)

CORS is configured to allow the custom x-original-authorization header:

ESPv2_ARGS: "^++^--cors_preset=basic++--cors_allow_methods=GET,HEAD,POST,PUT,DELETE,OPTIONS++--cors_allow_origin=*++--cors_allow_headers=keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,authorization,x-original-authorization++--cors_expose_headers=grpc-status,grpc-message"

Key addition: x-original-authorization is included in --cors_allow_headers

Backend Authentication (FirebaseAuthInterceptor)

The backend checks for tokens in order of preference:

  1. Primary: x-original-authorization header (bypasses ESPv2 JWT processing)
  2. Fallback: authorization header (for direct gRPC access)
// Check for Firebase token in custom header first (preferred)
String originalAuthHeader = headers.get(ORIGINAL_AUTH_HEADER);
String standardAuthHeader = headers.get(AUTHORIZATION_HEADER);

if (originalAuthHeader != null && !originalAuthHeader.trim().isEmpty()) {
// ESPv2 Mode: Use original Firebase token from custom header
authHeader = originalAuthHeader;
authMode = "ESPv2-Custom";
} else if (standardAuthHeader != null && !standardAuthHeader.trim().isEmpty()) {
// Direct Mode: Use Firebase token from standard Authorization header
authHeader = standardAuthHeader;
authMode = "Direct";
}

Deployment

Deploy ESPv2 configuration:

./env/deploy-endpoints.sh dev

This script:

  1. Downloads googleapis protobuf definitions
  2. Generates protobuf descriptors
  3. Deploys ESPv2 configuration to Cloud Endpoints
  4. Builds and deploys custom ESPv2 Docker image with CORS configuration

Testing

ESPv2 Proxy Access

# Get Firebase token
FIREBASE_TOKEN=$(gcloud auth print-identity-token --audiences=construction-code-expert-dev)

# Call via ESPv2 (frontend automatically adds both headers)
curl -H "authorization: Bearer ${FIREBASE_TOKEN}" \
-H "x-original-authorization: Bearer ${FIREBASE_TOKEN}" \
-H "content-type: application/json" \
https://construction-code-expert-esp2-dev-6yieikr6ca-uc.a.run.app/org.codetricks.construction.code.assistant.service.ArchitecturalPlanService/GetArchitecturalPlan \
-d '{"architectural_plan_id": "R2024.0091-2024-10-14"}'

Direct gRPC Access

# Get Firebase token  
FIREBASE_TOKEN=$(gcloud auth print-identity-token --audiences=construction-code-expert-dev)

# Call backend directly
grpcurl -H "authorization: Bearer ${FIREBASE_TOKEN}" \
-d '{"architectural_plan_id": "R2024.0091-2024-10-14"}' \
localhost:8080 \
org.codetricks.construction.code.assistant.service.ArchitecturalPlanService/GetArchitecturalPlan

Benefits of This Approach

1. Legitimate ESPv2 Configuration

  • Uses only documented CORS headers configuration
  • No non-existent or experimental flags
  • Standard ESPv2 Firebase authentication setup

2. Solves Audience Mismatch

  • Custom header bypasses ESPv2 JWT processing
  • Original Firebase token preserves correct audience (construction-code-expert-dev)
  • Backend validates original token with correct audience

3. Maximum Compatibility

  • ESPv2 proxy mode: Uses x-original-authorization header
  • Direct gRPC mode: Uses standard authorization header
  • Fallback support for both access patterns

4. Enhanced Security

  • Backend always validates actual Firebase tokens using Firebase Admin SDK
  • No spoofing vulnerabilities (validates cryptographic tokens)
  • ESPv2 provides additional authentication layer
  • Custom header approach maintains security while solving technical issues

Firebase Admin SDK Usage

Authentication

  • FirebaseAuthInterceptor validates all incoming tokens
  • Prefers x-original-authorization header (ESPv2 mode)
  • Falls back to authorization header (direct mode)
  • Uses FirebaseAuth.getInstance().verifyIdToken() for validation
  • Extracts user information (uid, email, name) from validated tokens

Custom Claims (RBAC)

  • RBACService.setPermissionsFromYaml() - loads role definitions from YAML
  • RBACService.setUserPermissions() - assigns roles to users
  • Custom claims structure: {"projects": {"project-id": "ADMIN"}}

Troubleshooting

Common Issues

  1. "Missing or invalid authorization header"

    • Ensure both Authorization and x-original-authorization headers are included
    • Verify token is not expired
  2. "Invalid Firebase token"

    • Check token audience matches project ID
    • Verify token was issued by correct Firebase project
    • Use x-original-authorization header to preserve original audience
  3. ESPv2 CORS errors with custom header

    • Verify x-original-authorization is included in cors_allow_headers
    • Check ESPv2 deployment includes updated CORS configuration

Debugging

Enable debug logging:

# ESPv2 debug mode already enabled in vars.yaml with --enable_debug

# Backend debug mode
export LOGGING_LEVEL_ORG_CODETRICKS=DEBUG

Check authentication mode in backend logs:

"Using custom x-original-authorization header (bypasses ESPv2 JWT processing)" - ESPv2 mode
"Using standard authorization header" - Direct mode

Token Inspection

Decode Firebase token payload:

# Split token and decode payload (part 2)
echo "$FIREBASE_TOKEN" | cut -d'.' -f2 | base64 -d | jq .

Security Considerations

  1. Token Validation: Always performed by Firebase Admin SDK on actual tokens
  2. No Header Spoofing: Backend validates cryptographic Firebase tokens, not derived headers
  3. Audience Verification: Original tokens maintain correct audience claims
  4. Dual Authentication: ESPv2 + backend validation provides defense in depth
  5. Custom Header Security: x-original-authorization bypasses JWT processing but still contains validated tokens
  6. HTTPS Only: All communication uses TLS encryption