pub struct JwtVerifier { /* private fields */ }Expand description
PAS JWT verifier (RFC 9068, EdDSA).
Constructed once per consumer deployment with the JWKS URL and the
per-deployment VerifyConfig. Cheap to clone — internal state is
Arc-shared, so you can store the verifier behind
Arc<dyn BearerVerifier> in a request extension or per-route layer
without measurable overhead.
JwksCache and ArcClock have no useful Debug representation,
so this is a manual impl that surfaces only the expectations shape.
Implementations§
Source§impl JwtVerifier
impl JwtVerifier
Sourcepub async fn from_jwks_url(
jwks_url: impl Into<String>,
expectations: VerifyConfig,
epoch: EpochEnforcement,
) -> Result<JwtVerifier, VerifyError>
pub async fn from_jwks_url( jwks_url: impl Into<String>, expectations: VerifyConfig, epoch: EpochEnforcement, ) -> Result<JwtVerifier, VerifyError>
Single textbook constructor — collapses {JWKS construction, VerifyConfig, VerifyConfig} into one boundary call.
Consumer never sees the underlying JwksCache. The default
engine VerifyConfig::access_token(issuer, audience) covers
every concrete RCW/CTW/3rd-party scenario.
Required stance: epoch is the sv-axis EpochEnforcement
declaration (RFC_202607150428 Q3). There is no default — a
consumer that does not enforce the axis must pass a NAMED
EpochEnforcement::Unenforced (WARN-declared at boot) or
EpochEnforcement::EnforcedElsewhere; production perimeters
pass EpochEnforcement::Enforced over the canonical
Cache + Fetcher composition (pas_external::epoch:: CompositeEpochRevocation).
Builder family:
Self::with_audit— wire anAuditSinkfor M48 verify-failure emission (Phase 9). Defaults to no emission; wrap incrate::RateLimitedAuditSinkfor log-flood DoS defense (M49).Self::with_session_liveness— L2 session-row revocation.
§Errors
Returns VerifyError::KeysetUnavailable if the initial JWKS
fetch fails. The verifier cannot serve verifications without
at least one usable key snapshot.
Sourcepub fn with_clock(self, clock: Arc<dyn Clock>) -> JwtVerifier
pub fn with_clock(self, clock: Arc<dyn Clock>) -> JwtVerifier
Wire an audit sink for M48 verify-failure emission.
Every Err path inside BearerVerifier::verify will, after
this builder, also emit an AuditEvent through the supplied
sink. The sink is expected to be cheap (Arc-shared); it is
not consulted on the success path.
Composition: for log-flood DoS defense (M49), wrap the
real sink in crate::RateLimitedAuditSink before passing
here:
use std::sync::Arc;
use pas_external::{
AuditSink, MemoryRateLimiter, JwtVerifier, RateLimitedAuditSink,
};
let limited: Arc<dyn AuditSink> = Arc::new(RateLimitedAuditSink::new(
real_sink,
Arc::new(MemoryRateLimiter::default()),
));
let verifier = JwtVerifier::from_jwks_url(
"https://accounts.ppoppo.com/.well-known/jwks.json",
pas_external::VerifyConfig::new("accounts.ppoppo.com", "client-id"),
pas_external::EpochEnforcement::Unenforced {
reason: "doc example — pass Enforced(port) in production",
},
)
.await?
.with_audit(limited);Calling repeatedly replaces the sink (no chaining); the last
call wins. By design, only one sink per verifier — composition
for multiple destinations belongs in the adapter layer
(a FanoutAuditSink is a future possibility).
Inject an ArcClock for deterministic time control.
Sets the clock used for:
now: i64passed to the token engine on every verify callcreated_attimestamp inAuditEventfailure records- JWKS staleness check in the underlying
JwksCache
Defaults to WallClock. Tests inject FrozenClock to
control time-sensitive behavior without sleeping.
This builder is on JwtVerifier only (NOT on BearerVerifier
trait) — no breaking change for consumers holding
Arc<dyn BearerVerifier>.
pub fn with_audit(self, sink: Arc<dyn AuditSink>) -> JwtVerifier
Sourcepub fn with_session_liveness(
self,
port: Arc<dyn SessionLiveness>,
) -> JwtVerifier
pub fn with_session_liveness( self, port: Arc<dyn SessionLiveness>, ) -> JwtVerifier
Wire the L2 SessionLiveness port for per-request session-row
revocation enforcement (Phase 11.Z 0.10.0, RFC_2026-05-08 §4.2
lock). The L2 sibling of the required L1 EpochEnforcement ctor stance.
With no port wired, the verifier short-circuits the L2 check —
every token admits past the session-row axis (matching pre-0.10.0
behavior, where L2 was inline-composed in the consumer’s
AuthProvider::verify_token).
With a port wired, every verify call (after engine signature +
claims + L1 sv-axis succeed) consults port.check(sid) for the
bearer’s sid claim:
Ok(())→ admit.Err(SessionLivenessError::Revoked)→ reject asVerifyError::SessionRevoked(HTTP 401 + clear cookies in the perimeter).Err(SessionLivenessError::Transient)→ reject asVerifyError::SessionLivenessLookupUnavailable(HTTP 503, fail-closed perSTANDARDS_AUTH_INVALIDATION§3).
Lenient on no-sid claim: tokens without sid (machine
credentials, AI-agent flows, R6 legacy admit per
VerifiedClaims::session_id) admit without consulting the port.
Non-session-bound tokens have no row to look up. RFC_2026-05-08
§4.2 lock decision.
Canonical wiring shape — RCW post-0.10.0:
use std::sync::Arc;
use pas_external::SessionLiveness;
// RCW's PgSessionLiveness adapter against scrcall.user_sessions.
// CTW mirror: same shape over scctime.user_sessions.
verifier.with_session_liveness(liveness)Calling repeatedly replaces the port (no chaining); the last call wins.
Sourcepub fn for_test_skip_fetch(expectations: VerifyConfig) -> JwtVerifier
pub fn for_test_skip_fetch(expectations: VerifyConfig) -> JwtVerifier
Test-support ctor — constructs a verifier without performing a JWKS fetch.
The internal keyset is empty, so any engine verify path
rejects on KidUnknown (mapped to
VerifyError::SignatureInvalid per map_auth_error).
Adapter-side rejection paths
(VerifyError::InvalidFormat, VerifyError::IdTokenAsBearer)
are fully exercisable since they reject before consulting the
keyset.
Used by Phase 9.D’s audit-emission integration tests to drive
the verify path without a wiremock-shaped JWKS endpoint.
NOT for production use — use Self::from_jwks_url instead.
Sourcepub fn for_test_with_keys(
expectations: VerifyConfig,
keyset: KeySet,
epoch: EpochEnforcement,
) -> JwtVerifier
pub fn for_test_with_keys( expectations: VerifyConfig, keyset: KeySet, epoch: EpochEnforcement, ) -> JwtVerifier
Test-support ctor with a caller-supplied keyset, so verify can run
a full signature-passing path — reaching engine checks that run after
signature verification, the sv epoch gate in particular — without a
wiremock JWKS endpoint. Pair with ppoppo_token::SigningKey::test_pair()
to mint tokens the returned verifier will accept.
Trait Implementations§
Source§impl BearerVerifier for JwtVerifier
impl BearerVerifier for JwtVerifier
fn verify<'life0, 'life1, 'async_trait>(
&'life0 self,
bearer_token: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<VerifiedClaims, VerifyError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
JwtVerifier: 'async_trait,
Source§impl Clone for JwtVerifier
impl Clone for JwtVerifier
Source§fn clone(&self) -> JwtVerifier
fn clone(&self) -> JwtVerifier
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more