Created: 2026-04-09 by Diffora
- PRD — OIDC AuthN Resolver Plugin
- 1. Overview
- 2. Actors
- 3. Operational Concept & Environment
- 4. Scope
- 5. Functional Requirements
- 6. Non-Functional Requirements
- 7. Public Library Interfaces
- 8. Use Cases
- 9. Acceptance Criteria
- 10. Dependencies
- 11. Assumptions
- 12. Risks
- 13. Open Questions
- 14. Traceability
Abbreviation: AuthN = Authentication. Used throughout this document.
The OIDC AuthN Resolver Plugin provides authentication services for the Cyber Ware middleware by integrating with OpenID Connect-compliant Identity Providers (IdPs). It validates JWT access tokens, extracts identity claims, and produces the platform's SecurityContext structure that downstream modules consume for authorization and tenant-scoped access control.
The plugin is a vendor-specific implementation of the AuthN Resolver plugin interface, following the Cyber Ware gateway + plugin architecture pattern. It ships as the default authentication plugin for deployments using standard OIDC-compliant IdPs.
Cyber Ware modules need a consistent, high-performance mechanism to authenticate incoming API requests and establish caller identity. Without a centralized authentication plugin, each module would independently validate tokens, parse claims, and construct identity context — leading to duplicated logic, inconsistent claim interpretation, and security gaps.
The platform requires multi-tenant authentication where tenant isolation is achieved via claims embedded in access tokens rather than per-tenant IdP instances. A single OIDC issuer can serve many tenants (each tenant identified by a claim), while multiple issuers can coexist for different tenant groups. The target is 10,000+ tenants without requiring per-tenant IdP configuration.
Additionally, modules performing background work (scheduled jobs, event handlers) require authenticated SecurityContext instances without an incoming HTTP request. This necessitates a service-to-service credential exchange mechanism that follows the same authentication pipeline.
- Enable any OIDC-compliant IdP to serve as the platform identity provider without code changes — configuration only. The IdP must use UUID-format
subclaims (see Assumptions) and include a tenant identifier claim; IdPs that use non-UUID subject identifiers (e.g., Auth0auth0|…, Azure AD OIDs) require a protocol mapper / claim transformation to produce UUID-formatsubvalues. At launch: validated with at least two OIDC-compliant IdPs (e.g., Keycloak, Auth0). - Provide sub-5ms p95 authentication latency for the request hot path, eliminating the IdP as a per-request bottleneck. At launch: measured via load test at 10K rps with warm JWKS cache.
- Support claim-based multi-tenancy scaling to 10,000+ tenants per issuer. Multiple trusted issuers are supported for deployments that partition tenants across IdP instances. At launch: validated at 100 tenants; within 6 months: validated at 10,000+ tenants via scale test.
- Ensure zero authentication bypasses — every error path produces a rejection, never a default-allow. Continuously: verified via unit tests covering every error variant and CI enforcement.
| Term | Definition |
|---|---|
| AuthN | Authentication — verifying the identity of a caller. |
| AuthZ | Authorization — determining what an authenticated caller is allowed to do. |
| SecurityContext | Platform-wide identity structure containing subject ID, tenant ID, subject type, token scopes, and bearer token. Produced by the AuthN plugin, consumed by all downstream modules. |
| OIDC | OpenID Connect — an identity layer on top of OAuth 2.0 that provides standard endpoints for discovery, token validation, and identity claims. |
| JWKS | JSON Web Key Set — a set of cryptographic keys used to verify JWT signatures, published by the IdP. |
| S2S | Service-to-service — communication between platform modules without an end-user HTTP request. |
| IdP | Identity Provider — the external OIDC-compliant service that issues and manages access tokens. |
| First-party app | A platform-owned application (portal, CLI) that receives unrestricted scopes. |
| Third-party app | A partner integration that receives only its granted OAuth2 scopes. |
ID: cpt-cf-authn-plugin-actor-platform-admin
- Role: Configures the OIDC plugin — trusted issuers, claim mappings, audience settings, first-party client lists, S2S credentials.
- Needs: Clear configuration schema; fail-fast on misconfiguration; observable plugin health via metrics and logs.
ID: cpt-cf-authn-plugin-actor-api-gateway
- Role: Extracts bearer tokens from incoming HTTP
Authorizationheaders and delegates authentication to the AuthN Resolver gateway, which routes to this plugin.
ID: cpt-cf-authn-plugin-actor-domain-module
- Role: Consume the
SecurityContextproduced by the plugin for PEP (Policy Enforcement Point) decisions. Do not interact with the plugin directly — receiveSecurityContextvia middleware injection.
ID: cpt-cf-authn-plugin-actor-background-module
- Role: Invoke
exchange_client_credentialsto obtain an authenticatedSecurityContextfor background work (scheduled jobs, event handlers, async processing) when no incoming HTTP request is available.
ID: cpt-cf-authn-plugin-actor-idp
- Role: External system that issues JWT access tokens, publishes JWKS for signature verification, exposes OIDC Discovery endpoints, and provides a token endpoint for S2S credential exchange.
No module-specific environment constraints beyond project defaults. The plugin runs as an in-process library within the host module's runtime. All IdP communication uses HTTPS with certificate validation.
- JWT access token validation (local, via cached JWKS)
- OIDC Discovery auto-configuration (
.well-known/openid-configuration) - Configurable claim mapping to
SecurityContextfields - Claim-based multi-tenancy (tenant claim in token, one or more trusted issuers)
- First-party vs third-party app detection via client ID
- S2S client credentials exchange (OAuth2
client_credentialsgrant) - Configurable HTTP request timeout for all outbound IdP calls
- Transient-failure retry with exponential backoff + jitter (network errors, HTTP 5xx, HTTP 429)
- Per-host circuit breaker for IdP resilience (globally enable/disable)
- Plugin registration via Cyber Ware ClientHub with GTS identity
- Opaque token introspection (RFC 7662) — JWT-only; non-JWT tokens are rejected. If needed, implement as a separate plugin.
- Authorization decisions — AuthZ Resolver and Tenant Resolver handle policy evaluation, access filtering, and barrier enforcement downstream.
- Token issuance, refresh, or revocation — managed by the IdP.
- User management or identity provisioning — managed by the IdP.
- MFA enforcement — MFA is an IdP-side concern; tokens arriving at the plugin have already passed IdP authentication flows.
- Token revocation checking — JWT local validation cannot detect revoked tokens before expiry. Short token lifetimes (5–15 min) mitigate this. Real-time revocation requires a separate plugin.
-
p1- ID:cpt-cf-authn-plugin-fr-jwt-validation
The plugin MUST validate JWT access tokens locally using cached JWKS fetched from the IdP's standard jwks_uri endpoint. Validation MUST include: signature verification (RS256, ES256), exp claim validation (must be in future, with configurable clock skew leeway — default 60s), iss claim validation (must be in trusted issuers allowlist), and aud claim validation (when configured). The alg: none algorithm MUST never be accepted, even if misconfigured — tokens presenting alg: none MUST be rejected.
- Rationale: Local validation provides sub-millisecond latency and eliminates the IdP as a per-request bottleneck.
- Actors:
cpt-cf-authn-plugin-actor-api-gateway
-
p1- ID:cpt-cf-authn-plugin-fr-non-jwt-rejection
The plugin MUST reject tokens that are not valid JWTs (not three base64url-encoded segments) with Unauthorized("unsupported token format").
- Rationale: Scope constraint — this plugin handles JWT only. Prevents undefined behavior on unexpected token formats.
- Actors:
cpt-cf-authn-plugin-actor-api-gateway
-
p1- ID:cpt-cf-authn-plugin-fr-trusted-issuers
The plugin MUST maintain a configurable allowlist of trusted issuers. Tokens with an iss claim not matching any trusted issuer MUST be rejected with Unauthorized("untrusted issuer").
The jwt.trusted_issuers configuration MUST be an ordered array. Entries are evaluated from top to bottom, and the first matching entry determines the discovery URL.
Each trusted issuer entry MUST define exactly one of:
issuer— exact literal match against the tokenississuer_pattern— regular expression match against the tokeniss
Each entry MAY define discovery_url. If omitted, the plugin MUST use the token iss value as the discovery base URL. If discovery_url contains {issuer}, the placeholder MUST be replaced with the actual iss value from the token after the entry is matched.
When an entry matched via issuer_pattern is used, the matched pattern and the actual iss value MUST be logged at WARN level on first successful use to alert operators that pattern-based trust is active.
- Rationale: Ordered entries make precedence explicit, preserve fail-fast validation for invalid regexes at startup, and support deployments with many IdP instances sharing a common URL structure (e.g., per-tenant IdP partitions) without requiring an entry per instance. Optional
discovery_urlkeeps the common case (issis already the discovery base) simple while still supporting non-standard IdP layouts. - Actors:
cpt-cf-authn-plugin-actor-platform-admin,cpt-cf-authn-plugin-actor-api-gateway
-
p1- ID:cpt-cf-authn-plugin-fr-key-rotation
When a JWT presents a kid (Key ID) not found in the cached JWKS, the plugin MUST force-refresh the JWKS from the IdP. If the kid is still not found after refresh, the plugin MUST reject the token with Unauthorized("signing key not found").
- Rationale: IdPs rotate signing keys periodically; the plugin must handle this without manual intervention or downtime.
- Actors:
cpt-cf-authn-plugin-actor-idp
-
p2- ID:cpt-cf-authn-plugin-fr-audience-validation
When jwt.require_audience is true, the plugin MUST reject tokens without an aud claim. When jwt.expected_audience is configured, the plugin MUST verify that at least one aud value matches the expected audience list (with glob pattern support).
- Rationale: Audience validation prevents token confusion attacks where a token intended for a different service is presented to this platform.
- Actors:
cpt-cf-authn-plugin-actor-platform-admin
-
p1- ID:cpt-cf-authn-plugin-fr-claim-mapping
The plugin MUST extract claims from validated JWTs and map them to SecurityContext fields using configurable claim names:
-
subject_id(default:"sub") — MUST parse as UUID; reject if missing or invalid. -
subject_tenant_id(vendor-configured) — MUST parse as UUID; reject if missing or invalid (seecpt-cf-authn-plugin-fr-tenant-claim). -
subject_type(vendor-configured, optional) —Nonewhen absent. -
token_scopes(default:"scope") — split on spaces. -
Rationale: Different IdPs use different claim names for the same semantic fields. Configurable mapping enables IdP-agnostic operation.
-
Actors:
cpt-cf-authn-plugin-actor-platform-admin
-
p1- ID:cpt-cf-authn-plugin-fr-tenant-claim
The plugin MUST require a tenant identifier claim in every access token (claim name is vendor-configurable, e.g., "tenant_id", "org_id", "account_id"). The claim value MUST parse as UUID (RFC 4122). Tokens without the tenant claim MUST be rejected with Unauthorized("missing tenant_id"); tokens with a non-UUID tenant claim MUST be rejected with Unauthorized("invalid tenant id").
- Rationale: Tenant isolation is foundational to the platform's multi-tenancy model. Allowing tokens without tenant identity would break downstream tenant-scoped access control.
- Actors:
cpt-cf-authn-plugin-actor-api-gateway
-
p2- ID:cpt-cf-authn-plugin-fr-first-party-detection
When jwt.first_party_clients is configured, the plugin MUST check the token's client_id/azp claim against the list. First-party apps MUST receive token_scopes = ["*"] (unrestricted). Third-party apps MUST receive only their granted OAuth2 scopes from the token.
- Rationale: Platform-owned applications (portal, CLI) should not be capability-restricted by token scopes. Third-party integrations must be limited to their granted scopes.
- Actors:
cpt-cf-authn-plugin-actor-platform-admin
-
p1- ID:cpt-cf-authn-plugin-fr-oidc-discovery
The plugin MUST fetch and cache the IdP's .well-known/openid-configuration document to resolve jwks_uri and token_endpoint dynamically. Only the issuer URL needs to be configured.
- Rationale: Eliminates manual endpoint configuration; standard OIDC Discovery makes the plugin work with any compliant IdP.
- Actors:
cpt-cf-authn-plugin-actor-idp
-
p1- ID:cpt-cf-authn-plugin-fr-jwks-caching
The plugin MUST cache JWKS key sets in memory with configurable TTL (default: 1h) and max entries (default: 10). When the cache reaches max_entries, it MUST evict the least-recently-used (LRU) entry. Cache MUST be refreshable on unknown kid with a configurable minimum refresh interval to prevent abuse.
- Rationale: Caching eliminates per-request IdP network calls for the common case, achieving sub-millisecond validation latency.
- Actors:
cpt-cf-authn-plugin-actor-api-gateway
-
p1- ID:cpt-cf-authn-plugin-fr-s2s-exchange
The plugin MUST implement OAuth2 client_credentials grant (RFC 6749 §4.4) to obtain access tokens for service-to-service communication. The obtained JWT MUST be validated through the same pipeline as bearer token authentication. The resulting SecurityContext MUST include the obtained access token as bearer_token.
- Rationale: Modules performing background work need authenticated
SecurityContextinstances without an incoming HTTP request. - Actors:
cpt-cf-authn-plugin-actor-background-module
-
p2- ID:cpt-cf-authn-plugin-fr-s2s-caching
The plugin MUST cache S2S authentication results keyed by (client_id, normalized_scopes, credential_fingerprint) with TTL = min(token expires_in, s2s_oauth.token_cache.ttl).
normalized_scopes means the requested scope set after whitespace normalization, deduplication, lexicographic sorting, and joining with a single space. Missing or empty scopes MUST normalize to the empty set.
credential_fingerprint MUST be derived from client_secret without storing the raw secret in the cache key.
Concurrent cache misses for the same full cache key MUST NOT cause duplicate IdP requests. When the S2S result cache reaches s2s_oauth.token_cache.max_entries, it MUST evict the least-recently-used (LRU) entry. TTL expiry still applies independently.
- Rationale: Avoids repeated IdP round-trips for the same S2S token variant while preventing reuse across different scope sets or credential values; also prevents thundering herd on cache miss.
- Actors:
cpt-cf-authn-plugin-actor-background-module
-
p3- ID:cpt-cf-authn-plugin-fr-s2s-default-subject-type
When s2s_oauth.default_subject_type is configured and the obtained S2S token does not contain a subject_type claim, the plugin MUST apply the configured default value as SecurityContext.subject_type.
- Rationale: Many IdPs omit type claims from
client_credentialstokens. This ensuresSecurityContext.subject_typeis always populated for S2S flows. - Actors:
cpt-cf-authn-plugin-actor-platform-admin
-
p1- ID:cpt-cf-authn-plugin-fr-clienthub-registration
The plugin MUST register dyn AuthNResolverPluginClient with Cyber Ware ClientHub using GTS schema identity at startup. Registration MUST include vendor key ("cyberfabric"), priority, and display name.
- Rationale: Enables the AuthN Resolver gateway to discover and select the active plugin at runtime via ClientHub lookup.
- Actors:
cpt-cf-authn-plugin-actor-api-gateway
Breaking config change:
discovery_timeoutis removed. Its role is replaced byhttp_client.request_timeout(default5s), which applies uniformly to every outbound IdP HTTP call (discovery, JWKS, S2S token endpoint). Deployments that set a non-defaultdiscovery_timeoutMUST migrate tohttp_client.request_timeout.
A complete operator configuration example is maintained at config-example.yaml.
-
p1- ID:cpt-cf-authn-plugin-fr-request-timeout
The plugin MUST apply a configurable http_client.request_timeout (default 5s) to every outbound IdP HTTP call — OIDC discovery, JWKS fetch, and the S2S token endpoint. The timeout MUST be applied per attempt; a retried call receives a fresh request_timeout on each attempt.
- Rationale: Bounds worst-case latency contributed by IdP calls and prevents unbounded requests from exhausting connection pools. A single knob keeps operator configuration simple.
- Actors:
cpt-cf-authn-plugin-actor-platform-admin
-
p1- ID:cpt-cf-authn-plugin-fr-retry-policy
The plugin MUST retry transient outbound IdP failures with exponential backoff plus jitter, bounded by a configurable retry policy:
-
Retryable: connection errors (DNS, refused, TLS, reset), HTTP 5xx, HTTP 429.
-
Not retryable: request timeout (a slow failure — retrying multiplies user-facing latency and adds load to a struggling IdP; repeated timeouts are absorbed by the per-host circuit breaker instead), HTTP 4xx other than 429 (permanent), 2xx with unparseable body.
-
retry_policy.max_attempts(default3) MUST bound the number of retry attempts after the initial call.0MUST disable retries.max_attempts = 3means the worst case is 1 initial call + 3 retries = 4 total requests. -
When an HTTP 429 response includes a
Retry-Afterheader, the plugin MUST honor it, capped byretry_policy.max_backoff. -
Retries MUST occur inside each circuit-breaker call — one logical operation MUST equal one breaker attempt, so exhausted retries count as a single failure toward the breaker's
failure_threshold. -
Rationale: Transient blips (one-off network hiccups, brief IdP 5xx, rate-limit bursts) should not surface as failed authentications. Inside-the-breaker semantics prevent retry amplification from prematurely tripping the breaker.
-
Actors:
cpt-cf-authn-plugin-actor-platform-admin,cpt-cf-authn-plugin-actor-api-gateway
-
p1- ID:cpt-cf-authn-plugin-fr-circuit-breaker
The plugin MUST maintain circuit-breaker state keyed by the outbound HTTP host so that degradation of one IdP host does not block calls to other hosts (including other trusted issuers served by a different host). When a host's breaker is Open, calls to that host that require a fresh IdP response MUST fail with ServiceUnavailable; calls to other hosts MUST continue unaffected. The breaker MUST be globally enable/disable-able via circuit_breaker.enabled (default true). When disabled, every host MUST behave as if permanently Closed — retries and http_client.request_timeout still apply.
- Rationale: The plugin already supports multiple trusted issuers that can live on different IdP hosts with independent availability. A single shared breaker would cause one flaky IdP to block authentication against a healthy one. The global toggle lets operators disable breaker behavior in test/dev or when relying solely on caching + retry.
- Actors:
cpt-cf-authn-plugin-actor-platform-admin,cpt-cf-authn-plugin-actor-api-gateway
-
p1- ID:cpt-cf-authn-plugin-nfr-jwt-latency
JWT local validation MUST complete within 5ms at p95 with a warm JWKS cache at 10K requests per second.
- Threshold: p95 ≤ 5ms, p99 ≤ 10ms (warm cache)
- Rationale: Authentication is on the critical request path — every API call flows through it. Latency here directly impacts user-perceived response times.
- Architecture Allocation: See DESIGN.md § NFR Allocation — Token Validator + JWKS Cache
-
p1- ID:cpt-cf-authn-plugin-nfr-availability
Plugin availability MUST be ≥ 99.99%. When the IdP is unreachable, the plugin MUST continue to validate JWTs using cached JWKS (stale-while-revalidate). A per-host circuit breaker (globally toggleable via circuit_breaker.enabled) MUST prevent cascade failure when one IdP host is degraded, without blocking calls to other healthy hosts. Transient outbound failures (connection errors, HTTP 5xx, HTTP 429) MUST be retried per a configurable retry policy before being treated as terminal.
- Threshold: ≥ 99.99% availability
- Rationale: Authentication unavailability blocks all authenticated API traffic. Cached JWKS, bounded retries, and a per-host circuit breaker ensure continued operation during IdP degradation and isolate blast radius when multiple trusted issuers are served by different IdP hosts.
- Architecture Allocation: See DESIGN.md § Reliability Architecture — Circuit Breaker + JWKS Cache
-
p1- ID:cpt-cf-authn-plugin-nfr-fail-closed
Every error path MUST return an explicit rejection error — never a default-allow. No authentication bypass is permitted under any failure condition.
- Threshold: Zero authentication bypasses under all failure modes
- Rationale: A single authentication bypass could expose all tenant data. This is the foundational security commitment.
- Architecture Allocation: See DESIGN.md § Principles — Fail-Closed Authentication
-
p1- ID:cpt-cf-authn-plugin-nfr-tenant-isolation
Zero cross-tenant data leaks via the authentication layer. Tokens without a valid tenant claim MUST be rejected.
- Threshold: Zero cross-tenant leaks
- Rationale: Multi-tenancy is the platform's core value proposition. AuthN-level tenant isolation is the first line of defense.
- Architecture Allocation: See DESIGN.md § Principles — Claim-Based Tenant Isolation
-
p2- ID:cpt-cf-authn-plugin-nfr-s2s-latency
S2S credential exchange MUST complete within 500ms at p95 (cold cache, including IdP round-trip). Warm cache (token cached) MUST complete within 1ms at p95.
- Threshold: p95 ≤ 500ms cold, p95 ≤ 1ms warm
- Rationale: Background tasks should not be delayed by authentication overhead. Token caching ensures sub-millisecond for the common case.
- Architecture Allocation: See DESIGN.md § NFR Allocation — Token Client + Token Cache
-
p1- ID:cpt-cf-authn-plugin-nfr-security
Bearer tokens and client secrets MUST never be logged, persisted, or included in debug output. Tokens MUST be wrapped in SecretString types that are excluded from Debug/Display/serialization.
- Threshold: Zero token exposures in logs or debug output
- Rationale: Token leakage enables session hijacking and impersonation across all tenants.
- Architecture Allocation: See DESIGN.md § Security Architecture
- Database performance: Not applicable — the plugin has no persistent database storage; in-memory caches only.
- Frontend performance: Not applicable — backend authentication component with no user-facing interface.
-
p1- ID:cpt-cf-authn-plugin-interface-gateway -
Type: Rust async trait (
AuthNResolverClient) -
Stability: stable
-
Description: Public API for platform modules. Provides
authenticate(bearer_token)andexchange_client_credentials(request). Delegates to the configured plugin. -
Breaking Change Policy: Major version bump required; within a version, only additive changes.
-
p1- ID:cpt-cf-authn-plugin-interface-plugin -
Type: Rust async trait (
AuthNResolverPluginClient) -
Stability: stable
-
Description: Implemented by each vendor-specific plugin. Provides
authenticate(bearer_token)andexchange_client_credentials(request). -
Breaking Change Policy: Major version bump required; within a version, only additive changes (new methods with default implementations).
-
p1- ID:cpt-cf-authn-plugin-contract-oidc-idp -
Direction: required from external system
-
Protocol/Format: HTTPS GET (discovery, JWKS), HTTPS POST (token endpoint)
-
Compatibility: OIDC Discovery 1.0, RFC 7517 (JWKS), RFC 6749 §4.4 (client_credentials grant)
-
p1- ID:cpt-cf-authn-plugin-contract-security-context -
Direction: provided by library
-
Protocol/Format: Rust struct (
SecurityContextfrommodkit-security) -
Compatibility: Platform
SecurityContextbuilder contract; fields:subject_id,subject_tenant_id,subject_type,token_scopes,bearer_token.
-
p1- ID:cpt-cf-authn-plugin-usecase-authenticate-request
Actor: cpt-cf-authn-plugin-actor-api-gateway
Preconditions:
- Plugin is registered with ClientHub
- Trusted issuers and claim mappings are configured
- JWKS is cached (warm) or IdP is reachable (cold)
Main Flow:
- API Gateway extracts bearer token from
Authorizationheader. - Gateway calls
authenticate(bearer_token)on AuthN Resolver. - Plugin validates JWT signature, expiry, issuer, and audience.
- Plugin extracts claims and maps to
SecurityContext. - Plugin returns
AuthenticationResultcontainingSecurityContext. - Middleware injects
SecurityContextinto the request for downstream PEP.
Postconditions:
SecurityContextis populated withsubject_id,subject_tenant_id,token_scopes.
Alternative Flows:
- Invalid token: Plugin returns
Unauthorizedwith reason. Middleware returns 401 to client. - IdP unreachable, no cached JWKS: Plugin returns
ServiceUnavailable. Middleware returns 503 to client.
-
p2- ID:cpt-cf-authn-plugin-usecase-s2s-exchange
Actor: cpt-cf-authn-plugin-actor-background-module
Preconditions:
- S2S configuration (IdP discovery URL, client credentials) is provided
- Plugin is registered with ClientHub
Main Flow:
- Background module constructs a
ClientCredentialsRequest(containingclient_id,client_secret, andscopes) and callsexchange_client_credentials(request). - Plugin normalizes
scopes, derives acredential_fingerprintfromclient_secret, builds the S2S cache key, and checks token cache. - On cache miss: Plugin resolves token endpoint and performs
client_credentialsgrant. - Plugin validates the obtained JWT through the standard validation pipeline.
- Plugin maps claims to
SecurityContext, appliesdefault_subject_typeif needed. - Plugin caches the token and returns
AuthenticationResult.
Postconditions:
- Background module has an authenticated
SecurityContextwith a validbearer_token.
Alternative Flows:
- Cached token available: Plugin returns cached result without IdP call (sub-millisecond).
- Invalid credentials: Plugin returns
TokenAcquisitionFailed. No token is cached. - IdP unreachable, no cached S2S result: Plugin returns
ServiceUnavailable. No token is cached.
- JWT validation with warm JWKS cache completes within 5ms at p95 under 10K rps load.
- Tokens from untrusted issuers are rejected; tokens without
tenant_idclaim are rejected; non-JWT tokens are rejected; trusted issuer precedence is deterministic by declaration order. - JWKS key rotation is handled automatically (unknown
kidtriggers refresh). - S2S credential exchange produces a valid
SecurityContextwith cached token reuse. - Plugin registers with ClientHub and is discoverable by the AuthN Resolver gateway.
- No bearer tokens or client secrets appear in any log output.
- Circuit breaker opens per host after consecutive IdP failures; JWT validation continues with cached JWKS for that host; other hosts remain unaffected.
-
circuit_breaker.enabled: falsedisables tripping globally — every host behaves as permanently Closed; the plugin relies on retries and caches. - Transient IdP failures (connection errors, HTTP 5xx, HTTP 429) are retried up to
retry_policy.max_attemptsadditional attempts after the initial call (default3;0disables); HTTP 4xx other than 429 is not retried;Retry-Afteron 429 is honored (capped byretry_policy.max_backoff). - Every outbound IdP HTTP call respects
http_client.request_timeoutper attempt (default5s).
| Dependency | Description | Criticality |
|---|---|---|
authn-resolver-sdk |
SDK traits, models, error types, GTS schema definition | p1 |
modkit-security |
SecurityContext and SecurityContextBuilder types |
p1 |
modkit |
Plugin framework, GTS integration, ClientHub registration | p1 |
| OIDC Identity Provider | External IdP that issues tokens, publishes JWKS, exposes discovery and token endpoints | p1 |
jsonwebtoken crate |
JWT signature verification library (MIT) | p1 |
reqwest crate |
HTTP client for IdP communication (MIT/Apache-2.0) | p1 |
- The OIDC Identity Provider is compliant with OpenID Connect Discovery 1.0 and publishes a valid
.well-known/openid-configurationdocument. - Access tokens issued by the IdP are JWTs signed with RS256 or ES256 algorithms.
- The IdP includes a tenant identifier claim (vendor-configurable name) in every access token.
- The
subclaim in access tokens is a valid UUID (RFC 4122). Deployment constraint: Many OIDC providers use non-UUID subject identifiers by default. The IdP must be configured with a protocol mapper / claim transformation to produce UUID-formatsubvalues. Tokens with non-UUIDsubclaims are rejected withUnauthorized("invalid subject id"). - Token lifetimes are short (5–15 minutes), providing adequate revocation protection without explicit revocation checking.
- Each tenant is served by exactly one OIDC issuer. A single issuer may serve many tenants (via the
tenant_idclaim). Multiple issuers may coexist in the same deployment (each configured injwt.trusted_issuers), but a given tenant's users always originate from the same issuer.
| Risk | Impact | Mitigation |
|---|---|---|
| IdP outage blocks all authentication | All API requests fail with 503 | Stale-while-revalidate JWKS cache + bounded retry (retry_policy) for transient failures + per-host circuit breaker so degradation of one IdP host does not block calls to other hosts; JWT validation continues with cached keys |
CVE in jsonwebtoken crate |
Signature verification bypass — full authentication compromise | cargo-audit in CI; pin to reviewed versions; monitor RustSec advisories |
| IdP issues tokens without tenant claim | Tenant isolation broken for those tokens | tenant_id claim is required — tokens without it are rejected. IdP configuration must be validated during deployment |
| Token replay within expiry window | Unauthorized access using stolen valid token | Short token lifetimes (5–15 min) limit exposure window. For real-time revocation, a separate introspection plugin is needed |
| JWKS refresh flood (DoS) | Rate limit exhaustion or IdP overload | Rate-limited JWKS refresh (min 30s interval per issuer); single in-flight refresh; circuit breaker |
| # | Question | Impact | Owner | Target Date | Resolution |
|---|---|---|---|---|---|
| 1 | Should audience validation glob patterns support only * (substring wildcard) or also ? (single character)? Overly broad patterns could weaken audience enforcement. |
Security | Platform Architect | Before implementation start | Resolved in DESIGN: * substring wildcard only; no ? or ** support. See DESIGN.md § Token Validator jwt.expected_audience configuration. |
| 2 | What is the maximum acceptable JWKS staleness window when the IdP is unreachable? Indefinite stale-while-revalidate vs bounded grace period (e.g., 24h). | Availability vs Security | Platform Architect | Before implementation start | Resolved in DESIGN: Configurable via jwks_cache.stale_ttl (default 24h). Set to 0 to disable stale-while-revalidate. See DESIGN.md § OIDC Discovery configuration. |
- Design: DESIGN.md
- ADRs: No standalone ADRs — key decisions documented in platform ADRs (0002, 0003) and in DESIGN.md Principles/Constraints sections.
- Platform Auth Architecture:
docs/arch/authorization/DESIGN.md