Skip to main content

verify

Function verify 

Source
pub fn verify(
    claims: &TokenClaims,
    policy: &TokenPolicy<'_>,
    scopes: &dyn ScopePolicy,
    now: SystemTime,
) -> Result<AuthContext, Refusal>
Expand description

Turns claims an adapter authenticated into an AuthContext, or refuses.

§Errors

The Refusal naming the first rule claims fails. Order is deliberate: time is checked before audience so a stale token does not report a mismatch, and audience before issuer because the audience is the binding that keeps a token from crossing between servers, which is the one this crate must not get wrong.

§Examples

use std::time::{Duration, SystemTime};

use protocol_droid::application::oauth::{ScopePolicy, TokenClaims, TokenPolicy, verify};
use protocol_droid::domain::mcp::ResourceUri;
use protocol_droid::domain::registry::{Scope, ScopeSet};
use protocol_droid::domain::{ProtocolVersion, authorization_model};

struct Permissive;
impl ScopePolicy for Permissive {
    fn resolve(&self, scope: &str) -> Option<Scope> {
        match scope {
            "mcp:read" => Some(Scope::Read),
            _ => None,
        }
    }
}

let resource = ResourceUri::new("https://example.com/mcp")?;
let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000);
let policy = TokenPolicy::new(&resource, authorization_model(ProtocolVersion::V2026_07_28));

let good = TokenClaims::new()
    .with_subject("alice")
    .with_audience(resource.clone())
    .with_expiry(now + Duration::from_secs(60))
    .with_scope("mcp:read");

let context = verify(&good, &policy, &Permissive, now)?;
assert_eq!(context.principal(), "alice");
assert!(context.holds(ScopeSet::empty().with(Scope::Read)));

// A token minted for a different server is refused.
let elsewhere = TokenClaims::new()
    .with_subject("alice")
    .with_audience(ResourceUri::new("https://evil.example/mcp")?)
    .with_expiry(now + Duration::from_secs(60));
assert!(verify(&elsewhere, &policy, &Permissive, now).is_err());