Architecting Continuous Access Evaluation

An OAuth2 access token minted at 09:00 with a 60-minute TTL is still cryptographically valid at 09:59, even if the device it was issued to was flagged as compromised at 09:05. This is the core failure of static token-based authorisation in a zero-trust estate: authentication and authorisation are evaluated once, at issuance, and then trusted blindly until expiry. For an enterprise identity provider fronting tens of thousands of sessions, that window is an unacceptable blast radius. Continuous access evaluation closes this gap by converting authorisation from a point-in-time decision into a streaming, event-driven state machine that can revoke trust mid-session, not just at renewal.
This article covers the architecture required to implement continuous access evaluation using the OpenID Shared Signals Framework (SSF) and the Continuous Access Evaluation Protocol (CAEP), the failure modes that emerge when event delivery lags behind policy state, and the trade-offs between fail-open and fail-closed enforcement at scale.
#The Bottleneck: Stateless Tokens vs Stateful Risk
Standard OIDC/OAuth2 deployments treat the access token as a bearer credential. The resource server’s Policy Enforcement Point (PEP) validates the signature and expiry claims and nothing else. Risk signals — impossible travel, device posture drift, credential compromise, IP reputation changes — are detected asynchronously by a SIEM or a Conditional Access engine, but there is no mechanism to push that signal into an already-issued token. Shortening token TTLs to 5 minutes reduces the exposure window but multiplies token refresh traffic against the IdP by an order of magnitude, and it still leaves a non-zero gap.
Surviving the Final Domain Controller Cutover
Continuous access evaluation solves this by decoupling token validity from session trust. The token becomes a lightweight, short-lived artefact; the actual authorisation decision is re-evaluated against a live trust state held outside the token, on every request or on every signal event, whichever fires first.
#Architectural Breakdown
A production continuous access evaluation architecture has four components, mirroring the standard Zero Trust Architecture (per NIST SP 800-207) split between decision and enforcement:
- Identity Provider (IdP) / Transmitter — Issues tokens and emits Security Event Tokens (SETs, RFC 8417) whenever a risk-relevant event occurs (credential change, session revocation, device non-compliance).
- Shared Signals Framework (SSF) Stream — The transport layer. Either a push delivery (HTTPS POST to a receiver endpoint) or poll delivery (receiver pulls from a queue). This is the backbone of the OpenID CAEP specification.
- Policy Decision Point (PDP) — A stateful microservice that ingests SETs, mutates a trust registry (typically Redis or a distributed cache), and exposes a low-latency decision API.
- Policy Enforcement Point (PEP) — Sits at the API gateway or service meshsidecar (Envoy ext_authz, or an API Gateway Lambda authoriser) and calls the PDP synchronously on every inbound request before proxying to the resource server.The KBY LexiconService MeshA dedicated, infrastructure-layer abstraction that manages service-to-service communication via out-of-process proxies, decoupling network logic (retries, mTLS, observability, routing) from application code. It exists to provide uniform L4/L7 traffic control and zero-trust security across a distributed system without requiring per-language client libraries.Read Full Context
The critical architectural decision is where continuous access evaluation state lives. Embedding risk state inside the JWT (via a caep_events claim, for instance) reintroduces staleness because the token is immutable post-issuance. The trust registry must live external to the token, queried at request time.

#Event Flow
The following flow illustrates a session-revoked event propagating from the IdP through to enforcement at the gateway:
Rendering diagram...
#Implementation Logic
Rolling out continuous access evaluation across an existing OIDC estate follows a defined sequence rather than a big-bang cutover:
- Step 1 — Register the SSF receiver. The resource-side PDP registers with the IdP’s SSF configuration endpoint, negotiating supported event types (
session-revoked,token-claims-change,device-compliance-change,credential-change) and delivery method. - Step 2 — Shorten token TTL to a bounded ceiling. Access tokens drop to a 5–10 minute TTL. This is not the primary revocation mechanism, but a backstop for receivers that miss events entirely.
- Step 3 — Deploy the PDP as a sidecar-adjacent service. Co-locate the PDP decision cache with the gateway to keep the ext_authz round trip under 5ms at p99.
- Step 4 — Wire the PEP into the mesh. Every ingress path — API gateway, service meshsidecar, SPA backend-for-frontend — must call the PDP, not just the initial login flow.The KBY LexiconService MeshA dedicated, infrastructure-layer abstraction that manages service-to-service communication via out-of-process proxies, decoupling network logic (retries, mTLS, observability, routing) from application code. It exists to provide uniform L4/L7 traffic control and zero-trust security across a distributed system without requiring per-language client libraries.Read Full Context
- Step 5 — Instrument event lag. Track the delta between event emission timestamp and cache mutation timestamp; this delta is your continuous access evaluation exposure window and must be reported as an SLO.
#Code & Configurations
SSF stream configuration registered by the receiver against the IdP’s transmitter configuration endpoint:
1{
2 "delivery": {
3 "delivery_method": "https://schemas.openid.net/secevent/risc/delivery-method/push",
4 "endpoint_url": "https://pdp.internal.kby.io/ssf/receiver"
5 },
6 "events_requested": [
7 "https://schemas.openid.net/secevent/caep/event-type/session-revoked",
8 "https://schemas.openid.net/secevent/caep/event-type/token-claims-change",
9 "https://schemas.openid.net/secevent/caep/event-type/device-compliance-change"
10 ],
11 "format": "iss_sub"
12}A decoded Security Event Token (SET) payload for a session-revoked signal, per RFC 8417:
1{
2 "iss": "https://idp.kby.io",
3 "iat": 1719840000,
4 "jti": "c4f1-9a3e-71bb",
5 "aud": "https://pdp.internal.kby.io",
6 "events": {
7 "https://schemas.openid.net/secevent/caep/event-type/session-revoked": {
8 "subject": {
9 "format": "iss_sub",
10 "iss": "https://idp.kby.io",
11 "sub": "user-4471"
12 },
13 "reason": "device_noncompliant",
14 "initiating_entity": "policy-engine",
15 "event_timestamp": 1719839998
16 }
17 }
18}Envoy’s ext_authz HTTP filter configuration wiring the gateway to the PDP as the enforcement point:

1http_filters:
2 - name: envoy.filters.http.ext_authz
3 typed_config:
4 "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
5 transport_api_version: V3
6 http_service:
7 server_uri:
8 uri: http://pdp.internal.kby.io:9191
9 cluster: pdp_cluster
10 timeout: 0.05s
11 authorization_request:
12 headers_to_add:
13 - key: x-caep-check
14 value: "true"
15 failure_mode_allow: falseThe PDP’s core decision logic, expressed in Go pseudocode, resolves against the trust registry rather than re-parsing the JWT payload:
1func Authorize(ctx context.Context, sub, sid string) (bool, string) {
2 key := fmt.Sprintf("caep:trust:%s:%s", sub, sid)
3 state, err := redisClient.Get(ctx, key).Result()
4 if err == redis.Nil {
5 return true, "no_signal_default_allow" // bounded by short token TTL
6 }
7 if state == "revoked" {
8 return false, "session_revoked"
9 }
10 if state == "device_noncompliant" {
11 return false, "posture_failed"
12 }
13 return true, "trusted"
14}#Failure Modes & Edge Cases
Continuous access evaluation trades a static-token vulnerability for a distributed-systems consistency problem, and that trade needs explicit handling:
- Event delivery lag — Push delivery over HTTPS can queue behind receiver-side backpressure. If the PDP’s ingestion worker pool saturates during a mass credential rotation (e.g. a password-spray remediation forcing 10,000 session revocations), the SET queue backs up and the exposure window widens silently unless lag is actively monitored.
- Event storms — A directory-wide policy change (e.g. disabling legacy auth org-wide) can emit signals for every active session simultaneously. Without batching and idempotent event IDs (
jtideduplication), the trust registry can thrash under write amplification. - Replay attacks on SETs — Because SETs are bearer JWTs themselves, an attacker with network position could replay a stale
token-claims-changeevent to force an unintended trust state. Receivers must validateiatfreshness and maintain ajtinonce cache with a bounded TTL. - Receiver downtime — If the PDP receiver endpoint is unreachable, most transmitters queue and retry with exponential backoff, but retention windows are finite (typically 24–72 hours). A prolonged outage silently drops revocation signals, and the estate reverts to trusting tokens purely on TTL.
- Network partition between PDP and PEP — This is the fail-open/fail-closed decision point. If the gateway cannot reach the PDP, does it deny all traffic (safe, but a total availability failure) or allow traffic on stale cache state (available, but reintroduces the original vulnerability)?
#Scaling & Security Trade-offs
Deploying continuous access evaluation across a large estate forces explicit trade-offs that should be documented in the platform’s architectural patterns registry rather than left as tribal knowledge:
- Push vs poll delivery — Push delivery gives sub-second propagation but requires the receiver to expose a publicly reachable, authenticated endpoint, increasing attack surface. Poll delivery is safer at the network boundary but adds polling-interval latency (typically 5–30s) to the exposure window.
- Fail-open vs fail-closed PEP behaviour — Fail-closed is mandatory for regulated workloads (PCI, HIPAA scope) despite the availability cost; fail-open is acceptable only for low-sensitivity internal tooling where a PDP outage should not become a full outage.
- Centralised PDP vs sidecar-local cache — A single centralised PDP simplifies consistency but becomes a single point of contention at high RPS; sidecar-local caching (with async replication of trust state) reduces latency but reintroduces a small consistency lag per node.
- Token TTL vs refresh volume — Every reduction in token TTL directly increases refresh token grant volume against the IdP’s token endpoint; capacity planning for the IdP must scale proportionally, typically requiring horizontal read replicas on the token-signing service.
- Event retention vs storage cost — Retaining SET history for audit and replay-detection purposes (SOC2/ISO27001 evidentiary requirements) means the trust registry cannot be purely ephemeral; a write-once audit log (e.g. an append-only Kafka topic) should sit behind the Redis-backed hot cache.
None of these trade-offs are resolved generically — they are a function of the specific regulatory posture and latency budget of the workload sitting behind the PEP, and continuous access evaluation should be tuned per trust tier rather than applied uniformly across the estate.




