Skip to main content

OAuthAuthenticator

Struct OAuthAuthenticator 

Source
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>

Source

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,
);
Source

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.

Source

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");
Source

pub fn with_expected_audiences<I, T>(self, audiences: I) -> Self
where I: IntoIterator<Item = T>, T: Into<Audience>,

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).

Source

pub fn metadata(&self) -> &ResourceMetadata

The resource this authenticator protects.

Trait Implementations§

Source§

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>>

The context presented is allowed to act under.
Source§

impl<A> Debug for OAuthAuthenticator<A>

Source§

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.

Auto Trait Implementations§

§

impl<A> !RefUnwindSafe for OAuthAuthenticator<A>

§

impl<A> !UnwindSafe for OAuthAuthenticator<A>

§

impl<A> Freeze for OAuthAuthenticator<A>
where A: Freeze,

§

impl<A> Send for OAuthAuthenticator<A>
where A: Send,

§

impl<A> Sync for OAuthAuthenticator<A>
where A: Sync,

§

impl<A> Unpin for OAuthAuthenticator<A>
where A: Unpin,

§

impl<A> UnsafeUnpin for OAuthAuthenticator<A>
where A: UnsafeUnpin,

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

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

fn try_from(value: U) -> Result<T, !>

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