Authentication Boundaries¶
Value Fabric enforces strict authentication boundaries at every layer. No unauthenticated request may access protected resources. This page documents the OIDC/Keycloak integration, JWT validation rules, service-to-service authentication, session and token refresh semantics, and the dev auth bypass flags that are categorically blocked in production.
Production Invariant
No unauthenticated or improperly authenticated access to protected resources is permitted. The ProductionSafetyValidator will cause startup failure if any dev auth bypass flag is set in a production-like environment.
Authentication Architecture¶
The platform uses a layered middleware stack for request processing. The auth phase (order 3) validates credentials and establishes tenant context. Downstream code never re-validates authentication; it consumes the RequestContext established by the middleware.
| Phase | Order | Responsibility | Can Terminate |
|---|---|---|---|
| request_id | 1 | Assign x-request-id | No |
| correlation | 2 | Extract/inject correlation IDs | No |
| auth | 3 | Validate credentials, establish tenant context | Yes (401/403) |
| tenant_scope | 4 | Validate tenant access | Yes (403) |
| rate_limit | 5 | Apply rate limiting | Yes (429) |
| validation | 6 | Validate request against OpenAPI | Yes (400) |
| handler | 7 | Execute business logic | Yes |
| error_boundary | Global | Catch errors, normalize shape | Yes |
OIDC / Keycloak Integration¶
User-facing authentication is delegated to Keycloak via the OpenID Connect (OIDC) protocol. The platform does not implement its own password storage for end users.
- Identity Provider: Keycloak (self-hosted or external OIDC-compatible IdP)
- Protocol: OpenID Connect (Authorization Code Flow with PKCE)
- Token Format: JWT (JSON Web Token)
- Signing Algorithms: HS256, RS256, or ES256 only
Token Claims¶
Every JWT issued by the platform's auth boundary must contain the following claims:
| Claim | Purpose | Example |
|---|---|---|
sub | User identifier (UUID) | "user-123" |
tenant_id | Tenant scope (UUID) | "tenant-a" |
role | RBAC role | "standard", "admin", "read_only" |
exp | Expiration timestamp | 1704067200 |
iat | Issued-at timestamp | 1704063600 |
iss | Token issuer | "https://auth.example.com" |
aud | Intended audience | "value-fabric-api" |
JWT claim precedence
The tenant_id claim inside the JWT takes precedence over any X-Tenant-ID header or request body field. Header-based tenant spoofing is ignored. Attempts to supply a mismatched X-Tenant-ID header are either rejected with 403 or the JWT tenant is enforced.
Token Validation¶
The platform enforces strict JWT validation at the middleware layer. The following tests in tests/security/test_auth_boundaries.py verify these invariants:
Rejected Token Types (401)¶
| Scenario | Expected Behavior | Test Reference |
|---|---|---|
Missing Authorization header | 401 + WWW-Authenticate header | test_no_auth_header_rejected |
| Empty / whitespace-only header | 401 | test_empty_auth_header_rejected |
Invalid Bearer token format | 401 | test_invalid_token_format_rejected |
Basic auth prefix | 401 | test_wrong_token_prefix_rejected |
| Gibberish / random string token | 401 | test_gibberish_token_rejected |
| SQL injection in token payload | 401 | test_sql_injection_in_token_blocked |
| XSS attempt in token payload | 401 + no reflection | test_xss_in_token_sanitized |
| Truncated JWT (missing signature) | 401 | test_truncated_token_rejected |
| Extra JWT parts | 401 | test_extra_parts_token_rejected |
| Invalid base64 | 401 | test_invalid_base64_token_rejected |
| Empty payload JWT | 401 | test_empty_payload_token_rejected |
| Expired JWT | 401 + expired detail | test_expired_token_rejected |
JWT with iat in the future | 401 (clock skew protection) | test_future_issued_jwt_rejected |
JWT with none algorithm | 401 / 403 | test_none_algorithm_rejected |
| Algorithm confusion (RS256 → HS256) | 401 / 403 | test_algorithm_confusion_attack_blocked |
| Tampered signature | 401 | test_invalid_signature_rejected |
Modified role claim | 401 / 403 | test_modified_role_claim_rejected |
Modified tenant_id claim | 401 / 403 | test_modified_tenant_claim_rejected |
Algorithm policy
Only HS256, RS256, and ES256 are permitted. The none algorithm and weak algorithms (HS1, HS384, HS512 without explicit validation) are rejected. Algorithm confusion attacks (e.g., claiming RS256 but signing with HMAC) are blocked.
Role-Based Access Control (RBAC)¶
RBAC is validated on every request, not just at login. The permission model supports both OR-logic (has_any_permission) and AND-logic (has_all_permissions).
| Role | Typical Permissions |
|---|---|
read_only | read only |
standard | read, limited write |
advanced | read, write, formula access |
admin | Full CRUD, admin endpoints, user management |
Permission bypass prevention
Wildcard permissions (permissions: ["*"]) are discarded during JWT context extraction. The has_any_permission OR-logic returns False when the permission set is empty. The has_all_permissions AND-logic requires every listed permission; partial matches are rejected. These invariants are tested in tests/security/test_rbac.py.
API Key Authentication¶
API keys use HMAC-SHA256 for fast verification (not bcrypt). Keys are scoped to a specific tenant and cannot escalate beyond the associated user's role.
# Secure: API key scoped to tenant
headers = {"X-API-Key": "test-tenant-a-key"}
# Insecure (blocked): API key with attempted cross-tenant header
headers = {
"X-API-Key": "test-tenant-a-key",
"X-Tenant-ID": "tenant-b", # Rejected — key is scoped to tenant-a
}
Service-to-Service Authentication¶
Internal communication between layers (L1–L6) uses JWT-based service authentication. Each service validates the calling service's token against a shared secret or public key.
| Concern | Pattern |
|---|---|
| Cross-service header | x-fabric-tenant-id with signature verification |
| Message queue | Explicit tenant_id field in every Celery/Redis payload |
| Service identity | service_account_id claim in JWT |
| Validation | Same JWT middleware as user tokens, with additional iss/aud checks |
Tests in tests/security/test_l1l2_service_to_service_jwt.py and tests/security/test_cross_stack_jwt_contract.py enforce that service-to-service tokens: - Are validated with the same strictness as user tokens - Carry correct tenant context - Cannot be replayed across service boundaries
Session Management¶
- Token lifetime: Short-lived access tokens (recommended: 15 minutes)
- Refresh tokens: Long-lived refresh tokens stored securely by the frontend; rotation is enforced
- Session correlation:
x-fabric-session-idheader for cross-request continuity - Logout: Token revocation list (Redis) for immediate invalidation
Token Refresh¶
The OIDC refresh endpoint (/auth/oidc/{provider}/refresh) is rate-limited independently from login and callback endpoints. Rate limit keys include both tenant and user dimensions to prevent cross-tenant refresh abuse.
# Rate limit key format for authenticated auth-scope requests
ratelimit:user:{tenant_id}:{user_id}:auth
Dev Auth Bypass Flags¶
NEVER enable in production
The following environment variables are for local development only. They are validated by ProductionSafetyValidator and will cause startup failure in production-like environments:
| Variable | Purpose | Production Effect |
|---|---|---|
DEV_AUTH_BYPASS=true | Skip auth checks in dev | Startup failure |
ALLOW_DEV_AUTH_BYPASS=true | Allow dev bypass | Startup failure |
AUTH_BYPASS_ENABLED=true | Enable auth bypass | Startup failure |
ALLOW_INSECURE_DEV_AUTH_BYPASS=true | Insecure bypass | Startup failure |
Bypass Flag Enforcement¶
The validate_production_safety() function (from value_fabric.shared.security.config) is called during service startup. It rejects: - All bypass flags in production, prod, staging, stage, and preprod environments - DEBUG=true in production - Case-insensitive and whitespace-padded variants (TRUE, True, 1, yes, true)
# tests/security/test_dev_bypass.py
@pytest.mark.parametrize("bypass_var,bypass_value", _BYPASS_VARS)
def test_bypass_flag_rejected_in_production(monkeypatch, bypass_var, bypass_value):
_set_base_env(monkeypatch, "production")
monkeypatch.setenv(bypass_var, bypass_value)
with pytest.raises(RuntimeError, match=bypass_var):
validate_production_safety(environment="production")
In development, bypass flags are allowed but emit a WARNING log so operators are aware.
Frontend Auth Bypass Detection¶
The frontend build pipeline includes a dedicated test that asserts no dev auth bypass code is present in the production bundle:
This scans the built output for strings like DEV_AUTH_BYPASS, mock auth providers, or hardcoded tokens.
Secure vs Insecure Patterns¶
Secure¶
# Extract tenant from authenticated context, never from the request body
tenant_id = ctx.tenant_id
repo.method(..., tenant_id=tenant_id)
# Validate JWT with explicit allowed algorithms
jwt.decode(token, secret, algorithms=["HS256", "RS256"], options={"require": ["exp", "iat"]})
# Rate-limit auth endpoints by tenant + user
key = f"ratelimit:user:{tenant_id}:{user_id}:auth"
Insecure (Blocked by CI)¶
# NEVER trust request body tenant IDs
tenant_id = request.json().get("tenant_id") # BLOCKED
# NEVER allow the 'none' algorithm
jwt.decode(token, secret, algorithms=["none"]) # BLOCKED
# NEVER hardcode bypass flags in application code
if os.getenv("DEV_AUTH_BYPASS") == "true": # BLOCKED in production
skip_auth()
Validation Commands¶
# Auth boundary tests
pytest tests/security/test_auth_boundaries.py -v
# RBAC and permission logic
pytest tests/security/test_rbac.py -v
pytest tests/security/test_rbac_expanded.py -v
# Default-deny behavior
pytest tests/security/test_auth_default_deny.py -v
# JWT configuration validation
pytest tests/security/test_jwt_config_validation.py -v
pytest tests/security/test_jwt_validation.py -v
pytest tests/security/test_jwt_rotation.py -v
# Dev bypass guardrails
pytest tests/security/test_dev_bypass.py -v
pytest tests/security/test_production_bypass_guardrails.py -v
# OIDC-specific tests
pytest tests/security/test_oidc.py -v
# Rate limiting on auth endpoints
pytest tests/security/test_auth_rate_limiting.py -v