Skip to main content

JwtVerifier

Struct JwtVerifier 

Source
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

Source

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:

§Errors

Returns VerifyError::KeysetUnavailable if the initial JWKS fetch fails. The verifier cannot serve verifications without at least one usable key snapshot.

Source

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: i64 passed to the token engine on every verify call
  • created_at timestamp in AuditEvent failure 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>.

Source

pub fn with_audit(self, sink: Arc<dyn AuditSink>) -> JwtVerifier

Source

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:

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.

Source

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.

Source

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

Source§

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

Source§

fn clone(&self) -> JwtVerifier

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for JwtVerifier

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more