Skip to main content

Authenticator

Trait Authenticator 

Source
pub trait Authenticator: Send + Sync {
    // Required method
    fn authenticate<'a>(
        &'a self,
        presented: &'a Credentials<'_>,
    ) -> Pin<Box<dyn Future<Output = AuthContext> + Send + 'a>>;
}
Expand description

Resolves a request’s credentials into the context its handlers run under.

§Why this is async

Because establishing authenticity is network I/O. An introspecting source asks the authorization server about every request, and a JWKS source asks it for keys on rotation; neither can be answered from what is already in the process. A synchronous signature here would have to be served by an authenticator that cannot actually check a real token.

The boxed-future form is deliberate rather than an async fn in the trait. McpRouter holds Arc<dyn Authenticator>, so the port has to stay object safe, and native async fn on a trait is RPITIT and is not. It is written out rather than pulled in through async-trait for one method, and the allocation it adds is not what is being measured here — this runs once per HTTP request, beside a session lookup and a JSON parse, and the router was already dispatching through a dyn.

This is the “boxed futures are already the norm” case that the crate’s own rule carves out, and it is deliberately not what TokenSource does: that port takes impl TokenSource at the call site and never pays a boxing.

§Implementing this

Return AuthContext::anonymous for a request you do not recognise. Do not return a context with scopes for a credential you could not verify, and do not treat a malformed Authorization value as a present-but-empty one: a token that fails to parse is not a token.

Reporting why a credential failed is deliberately not in the signature. Every unusable credential arrives as the same anonymous context, and the policy turns that into one status code. An authenticator that could distinguish “expired” from “unknown key” would hand a caller an oracle for probing which tokens exist.

§Example

use std::future::Future;
use std::pin::Pin;

use protocol_droid::adapters::primary::transport::http::{
    Authenticator, Credentials,
};
use protocol_droid::domain::registry::{AuthContext, Scope, ScopeSet};

/// A stand-in for the real thing: it accepts exactly one well-known token
/// so the test has something to exercise. The real implementation is
/// [`OAuthAuthenticator`](super::auth::oauth::OAuthAuthenticator).
struct StaticToken;

impl Authenticator for StaticToken {
    fn authenticate<'a>(
        &'a self,
        presented: &'a Credentials<'_>,
    ) -> Pin<Box<dyn Future<Output = AuthContext> + Send + 'a>> {
        let context = match presented.authorization() {
            Some("Bearer test-token") => AuthContext::new(
                "test",
                ScopeSet::empty().with(Scope::Read),
            ),
            _ => AuthContext::anonymous(),
        };
        Box::pin(std::future::ready(context))
    }
}

Required Methods§

Source

fn authenticate<'a>( &'a self, presented: &'a Credentials<'_>, ) -> Pin<Box<dyn Future<Output = AuthContext> + Send + 'a>>

The context presented is allowed to act under.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§