Skip to main content

TokenPolicy

Struct TokenPolicy 

Source
pub struct TokenPolicy<'a> { /* private fields */ }
Expand description

What the request is allowed to be, given the resource it reached and the revision it negotiated.

Two deployment facts and one spec fact, all of which change whether a token is checked at all. Bundled because passing three arguments to every call site invites two of them being forgotten.

Implementations§

Source§

impl<'a> TokenPolicy<'a>

Source

pub const fn new(resource: &'a ResourceUri, model: AuthorizationModel) -> Self

A policy that accepts any issuer.

§Examples
use protocol_droid::domain::{ProtocolVersion, authorization_model};
use protocol_droid::domain::mcp::ResourceUri;
use protocol_droid::application::oauth::TokenPolicy;

let resource = ResourceUri::new("https://example.com/mcp")?;
let policy = TokenPolicy::new(&resource, authorization_model(ProtocolVersion::V2026_07_28));
assert!(policy.accepts_any_issuer());
Source

pub fn with_expected_issuer(self, issuer: impl Into<Arc<str>>) -> Self

Requires tokens to name issuer, refusing any minted by anyone else.

Source

pub fn with_expected_audience(self, audience: impl Into<Audience>) -> Self

Accepts tokens bound to audience as well as to the resource.

The single-audience form of Self::with_expected_audiences; see that method for what this is for and why the resource stays accepted.

§Examples
use protocol_droid::application::oauth::TokenPolicy;
use protocol_droid::domain::mcp::ResourceUri;
use protocol_droid::domain::{ProtocolVersion, authorization_model};

let resource = ResourceUri::new("https://example.com/mcp")?;
// A Microsoft Entra v2.0 token carries the web API's client ID in `aud`.
let policy = TokenPolicy::new(&resource, authorization_model(ProtocolVersion::V2026_07_28))
    .with_expected_audience("8f7c1e42-0b3a-4f5d-9c6e-1a2b3c4d5e6f");

assert_eq!(policy.expected_audiences().map(<[Audience]>::len), Some(1));
Source

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

Accepts tokens bound to any of audiences, as well as to the resource.

§Why this exists

The resource identifier and the aud claim are frequently two different strings, and binding them together refuses valid tokens. RFC 9728 §3.1 derives the metadata URL from the resource identifier, so the resource must be the HTTPS URL this server answers on — but that URL need not be what the authorization server writes into aud.

Microsoft Entra is the case that forced it. Validate the audience states that for v2.0 tokens the audience is the client ID of the web API — a bare GUID — while for v1.0 tokens it is one of the app ID URIs, such as api://{ApplicationID}. So three identifiers are in play, and conflating any two of them breaks:

identifierexamplerole
Application ID URIapi://<guid>what a client asks for as scope
client ID<guid>aud in a v2.0 token
RFC 9728 resourcehttps://host/mcpderives the metadata path; RFC 8707 indicator

A deployment fronted by Entra therefore configures the GUID here and keeps the HTTPS URL as the resource, and both verify.

§What this does not change

Comparison is still exact equality against each canonicalised ResourceUri, never “contains” and never a host or path prefix, so a sibling server’s token still fails. The resource identifier stays accepted, so adding audiences can only widen what this deployment already trusted — it never withdraws the RFC 8707 binding. Accepting an audience is a deliberate act by whoever holds the configuration; the default is None, which leaves behaviour exactly as it was.

A repeated call replaces the set rather than extending it, so the configured audiences are always the ones the last call named.

§Examples
use protocol_droid::application::oauth::TokenPolicy;
use protocol_droid::domain::mcp::ResourceUri;
use protocol_droid::domain::{ProtocolVersion, authorization_model};

let resource = ResourceUri::new("https://mcp.example.com/mcp")?;
let policy = TokenPolicy::new(&resource, authorization_model(ProtocolVersion::V2026_07_28))
    .with_expected_audiences([
        // A v2.0 client ID, and a v1.0 app ID URI.
        "8f7c1e42-0b3a-4f5d-9c6e-1a2b3c4d5e6f",
        "api://8f7c1e42-0b3a-4f5d-9c6e-1a2b3c4d5e6f",
    ]);

assert_eq!(policy.expected_audiences().map(<[Audience]>::len), Some(2));
Source

pub fn expected_audiences(&self) -> Option<&[Audience]>

The audiences accepted in addition to the resource, if any were set.

Source

pub const fn accepts_any_issuer(&self) -> bool

Whether an issuer check will be made.

Trait Implementations§

Source§

impl<'a> Clone for TokenPolicy<'a>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<'a> Debug for TokenPolicy<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a> Freeze for TokenPolicy<'a>

§

impl<'a> RefUnwindSafe for TokenPolicy<'a>

§

impl<'a> Send for TokenPolicy<'a>

§

impl<'a> Sync for TokenPolicy<'a>

§

impl<'a> Unpin for TokenPolicy<'a>

§

impl<'a> UnsafeUnpin for TokenPolicy<'a>

§

impl<'a> UnwindSafe for TokenPolicy<'a>

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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