pub struct OAuthAuthenticator<A> { /* private fields */ }Expand description
Authenticates bearer tokens against a TokenSource.
Generic over the source rather than holding a dyn, following
TokenSource’s own rule: the call site takes impl TokenSource, so this
monomorphises and the per-request cost is a direct call rather than a boxed
future behind a vtable. The scopes and the clock are dyn because a
deployment supplies them by value at construction and they are one call each.
Implementations§
Source§impl<A> OAuthAuthenticator<A>
impl<A> OAuthAuthenticator<A>
Sourcepub fn new(
source: A,
metadata: ResourceMetadata,
version: ProtocolVersion,
scopes: impl ScopePolicy + 'static,
clock: impl WallClock + 'static,
) -> Self
pub fn new( source: A, metadata: ResourceMetadata, version: ProtocolVersion, scopes: impl ScopePolicy + 'static, clock: impl WallClock + 'static, ) -> Self
An authenticator for source, protecting metadata’s resource under
the model version selects.
§Examples
use protocol_droid::adapters::primary::transport::http::{
OAuthAuthenticator, SystemClock,
};
use protocol_droid::application::oauth::NoScopes;
use protocol_droid::domain::{ProtocolVersion, mcp::{ResourceMetadata, ResourceUri}};
let metadata = ResourceMetadata::new(ResourceUri::new("https://example.com/mcp")?);
let authenticator = OAuthAuthenticator::new(
(), // a real deployment passes a `TokenSource`
metadata,
ProtocolVersion::V2025_06_18,
NoScopes,
SystemClock,
);Sourcepub fn with_expected_issuer(self, issuer: impl Into<Arc<str>>) -> Self
pub fn with_expected_issuer(self, issuer: impl Into<Arc<str>>) -> Self
Requires tokens to name issuer, rather than accepting any.
Off by default because RFC 9728 publishes a list of authorization servers this resource trusts, so a deployment fronted by more than one legitimately accepts more than one issuer. Turning it on is how a single-issuer deployment says so.
Sourcepub fn with_expected_audience(self, audience: impl Into<Audience>) -> Self
pub fn with_expected_audience(self, audience: impl Into<Audience>) -> Self
Accepts a token bound to audience, as well as to the resource.
For an authorization server whose aud is not this server’s RFC 9728
resource identifier. Microsoft Entra v2.0 is the motivating case: it
writes the web API’s client ID — a bare GUID — into aud, while the
resource has to stay the HTTPS URL because RFC 9728 §3.1 derives the
metadata path from it. Without this, every valid token is refused with
Refusal::AudienceMismatch.
The resource remains accepted, and comparison stays exact equality, so
this cannot be used to widen the check to a host or a path prefix.
See TokenPolicy::with_expected_audiences for the full argument.
§Examples
use protocol_droid::adapters::primary::transport::http::{
OAuthAuthenticator, SystemClock,
};
use protocol_droid::application::oauth::NoScopes;
use protocol_droid::domain::{ProtocolVersion, mcp::{ResourceMetadata, ResourceUri}};
let metadata = ResourceMetadata::new(ResourceUri::new("https://mcp.example.com/mcp")?);
let authenticator = OAuthAuthenticator::new(
(),
metadata,
ProtocolVersion::V2026_07_28,
NoScopes,
SystemClock,
)
// The Entra client ID of this web API — a bare GUID, which is not a
// URI, which is the point.
.with_expected_audience("8f7c1e42-0b3a-4f5d-9c6e-1a2b3c4d5e6f");Sourcepub fn with_expected_audiences<I, T>(self, audiences: I) -> Self
pub fn with_expected_audiences<I, T>(self, audiences: I) -> Self
Accepts a token bound to any of audiences, as well as to the resource.
The multi-identifier form of Self::with_expected_audience, for a
deployment that answers to more than one — a proxying one, or one fronted
by an authorization server that mints v1.0 tokens (app ID URI, e.g.
api://<guid>) as well as v2.0 ones (bare client ID GUID).
Sourcepub fn metadata(&self) -> &ResourceMetadata
pub fn metadata(&self) -> &ResourceMetadata
The resource this authenticator protects.
Trait Implementations§
Source§impl<A> Authenticator for OAuthAuthenticator<A>where
A: TokenSource,
impl<A> Authenticator for OAuthAuthenticator<A>where
A: TokenSource,
Source§fn 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>>
presented is allowed to act under.Source§impl<A> Debug for OAuthAuthenticator<A>
impl<A> Debug for OAuthAuthenticator<A>
Source§fn fmt(&self, f: &mut Formatter<'_>) -> Result
fn fmt(&self, f: &mut Formatter<'_>) -> Result
Prints the configuration only.
Written by hand rather than derived, and not for the usual reason of a
field that is awkward to format: the derive would demand
A: Debug on the source — which for a JWT source would mean formatting
its key set — and dyn Debug on two fields that are behaviour, not
state. What is left is what an operator actually needs when a 401
shows up: which resource is protected, under which revision’s model,
and whether an issuer was pinned. The expected issuer is printed;
nothing else on this type is secret, and no token ever reaches it.