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§
Sourcefn authenticate<'a>(
&'a self,
presented: &'a Credentials<'_>,
) -> Pin<Box<dyn Future<Output = AuthContext> + Send + 'a>>
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".