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):
- Frontend: Sends Firebase token in both
Authorizationandx-original-authorizationheaders - ESPv2: Processes
Authorizationheader, passes throughx-original-authorizationheader untouched - Backend: Validates original Firebase token from
x-original-authorizationheader
Direct gRPC Mode (Testing):
- Frontend: Sends Firebase token in
Authorizationheader - Backend: Validates Firebase token from
Authorizationheader
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-authorizationbypasses 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:
- Primary:
x-original-authorizationheader (bypasses ESPv2 JWT processing) - Fallback:
authorizationheader (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:
- Downloads googleapis protobuf definitions
- Generates protobuf descriptors
- Deploys ESPv2 configuration to Cloud Endpoints
- 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-authorizationheader - Direct gRPC mode: Uses standard
authorizationheader - 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
FirebaseAuthInterceptorvalidates all incoming tokens- Prefers
x-original-authorizationheader (ESPv2 mode) - Falls back to
authorizationheader (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 YAMLRBACService.setUserPermissions()- assigns roles to users- Custom claims structure:
{"projects": {"project-id": "ADMIN"}}
Troubleshooting
Common Issues
-
"Missing or invalid authorization header"
- Ensure both
Authorizationandx-original-authorizationheaders are included - Verify token is not expired
- Ensure both
-
"Invalid Firebase token"
- Check token audience matches project ID
- Verify token was issued by correct Firebase project
- Use
x-original-authorizationheader to preserve original audience
-
ESPv2 CORS errors with custom header
- Verify
x-original-authorizationis included incors_allow_headers - Check ESPv2 deployment includes updated CORS configuration
- Verify
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
- Token Validation: Always performed by Firebase Admin SDK on actual tokens
- No Header Spoofing: Backend validates cryptographic Firebase tokens, not derived headers
- Audience Verification: Original tokens maintain correct audience claims
- Dual Authentication: ESPv2 + backend validation provides defense in depth
- Custom Header Security:
x-original-authorizationbypasses JWT processing but still contains validated tokens - HTTPS Only: All communication uses TLS encryption