Skip to main content

HttpAuthLayerBuilder

Struct HttpAuthLayerBuilder 

Source
pub struct HttpAuthLayerBuilder<R = EmptyRefusal> { /* private fields */ }
Available on crate feature tower only.
Expand description

Builder for an enforcing HttpAuthLayer; see HttpAuthLayer::builder.

Implementations§

Source§

impl<R> HttpAuthLayerBuilder<R>

Source

pub fn static_token(self, token: impl Into<String>) -> Self

Accept this static token (compared in constant time). An empty string counts as no token.

§Security

Setting the token here bypasses accept_static_bearer, which only crate::static_token_policy reads; with OAuth configured, prefer HttpAuthLayerBuilder::build_with_decision. The token’s length is not hidden by the comparison, and Debug output shows it as <redacted>.

Source

pub fn optional_static_token(self, token: Option<String>) -> Self

Source

pub fn static_tokens(self, tokens: StaticTokens) -> Self

Accept every token in tokens (each compared in constant time, every entry every time; see StaticTokens), replacing a set given earlier. On a match the request’s extensions get Credential::StaticToken and the StaticTokenMatch naming the entry’s label.

Combines with the other static-token settings exactly as the axum layer’s AuthLayerBuilder::static_tokens does: with static_token, both are accepted (a secret in both counts once, under the set’s label); with build_with_decision, see that method. An empty set counts as no static token, so it does not satisfy the fail-closed build on its own.

§Security

Like static_token, this bypasses accept_static_bearer unless the layer is built with build_with_decision. Debug output shows the count and labels, never a secret.

§Examples
use oauth_resource_server::StaticTokens;
use oauth_resource_server::http_layer::HttpAuthLayer;

let tokens = StaticTokens::new()
    .with(Some("current"), "example-key-old")
    .and_then(|t| t.with(Some("next"), "example-key-new"))
    .unwrap();
let auth = HttpAuthLayer::builder().static_tokens(tokens).build().unwrap();
Source

pub fn optional_static_tokens(self, tokens: Option<StaticTokens>) -> Self

HttpAuthLayerBuilder::static_tokens when Some; None clears a set given earlier.

Source

pub fn oauth(self, validator: Arc<OAuthValidator>) -> Self

Accept OAuth access tokens this validator accepts.

Source

pub fn optional_oauth(self, validator: Option<Arc<OAuthValidator>>) -> Self

Source

pub fn sources( self, sources: impl IntoIterator<Item = CredentialSource>, ) -> Self

Where to read credentials from, replacing the default [CredentialSource::authorization_bearer()]. Every source is checked, whatever the others hold.

Source

pub fn static_challenge(self, challenge: Option<HeaderValue>) -> Self

The WWW-Authenticate challenge every 401 carries when NO OAuth validator is configured; with one, the validator’s challenges are used and this is ignored. Default: crate::DEFAULT_STATIC_CHALLENGE.

None sends no challenge at all and leaves any WWW-Authenticate an on_reject callback set untouched. That departs from RFC 9110 §15.5.2 (a 401 MUST carry a challenge); use it only to keep an existing API’s responses unchanged.

Source

pub fn optional(self) -> Self

Let a request that presents NO credential through, unauthenticated, with nothing inserted into its extensions; a credential that is presented but refused is refused exactly as without this. “No credential” is decided exactly as by the axum layer’s optional() (see its documentation): every value of every configured source header absent or blank, where a value that is not visible ASCII, a non-blank later value of a repeated header, a DPoP-scheme value and a tab-separated Bearer token all count as presented. Any Credential/AuthorizedToken an outer layer inserted is removed first.

§Security

Not a way around the fail-closed build: build still requires a static token or an OAuth validator. Every handler behind an optional layer must treat a request with no Credential in its extensions as unauthenticated.

Source

pub fn require_scopes( self, scopes: impl IntoIterator<Item = impl Into<String>>, ) -> Self

Require every scope in scopes (all-of) of every credential this layer accepts, on top of the validator’s own required_scopes — replacing scopes given earlier. The same validator, so the same key cache: no second validator is needed for routes that need more.

Behaves exactly as the axum layer’s AuthLayerBuilder::require_scopes: an OAuth token missing one is refused with 403 and a challenge naming the validator’s required scopes followed by these (see crate::refusal_for_scopes); a static token — it has no scopes — is refused the same way unless static_token_bypasses_scopes; an optional layer still passes a request that presents nothing. A layer that lets everything through (HttpAuthLayer::allow_unauthenticated) checks nothing, this included, which is why build_with_decision refuses an Unauthenticated decision on a builder with scopes (AuthLayerError::ScopesWithoutAuthentication) rather than drop them.

For a requirement on some routes only, put a RequireScopes layer on them instead, behind this one.

§Examples
use std::sync::Arc;

use oauth_resource_server::OAuthValidator;
use oauth_resource_server::http_layer::HttpAuthLayer;

let writes = HttpAuthLayer::builder()
    .oauth(oauth)
    .require_scopes(["docs:write"])
    .build()
    .unwrap();
Source

pub fn static_token_bypasses_scopes(self) -> Self

Let a static token pass require_scopes instead of refusing it with 403: the static token counts as holding every scope. Without require_scopes it changes nothing.

§Security

Opt in only where the static token is meant to be a full-access key.

Source

pub fn on_reject<B, F>(self, f: F) -> HttpAuthLayerBuilder<F>
where F: Fn(RejectContext<'_>) -> Response<B> + Send + Sync + 'static,

Build a refusal’s response (its body and any extra headers, such as Content-Type) — for an API whose errors are, say, JSON. Without it a refusal’s body is ResBody::default().

The callback shapes the response only; it cannot change the outcome. Whatever it returns, the status is set to RejectContext::status and WWW-Authenticate to the layer’s challenge, replacing any the callback set (only with static_challenge(None) and no OAuth are the callback’s headers left as they are). Never put TokenRejection::Invalid’s reason in the body.

§Examples
use http::{Response, header::CONTENT_TYPE};
use oauth_resource_server::http_layer::{HttpAuthLayer, RejectContext};

let auth = HttpAuthLayer::builder()
    .static_token("example-static-key")
    .on_reject(|cx: RejectContext<'_>| {
        Response::builder()
            .header(CONTENT_TYPE, "application/json")
            .body(format!(r#"{{"error":"{}"}}"#, cx.status.as_u16()))
            .unwrap()
    })
    .build()
    .unwrap();
Source

pub fn build_with_decision( self, decision: StaticTokenDecision, ) -> Result<HttpAuthLayer<R>, AuthLayerError>

Build the layer a crate::static_token_policy decision calls for, keeping this builder’s other settings. The decision’s static token (if any) replaces one set on this builder; StaticTokenDecision::Unauthenticated yields the allow_unauthenticated pass-through.

A static_tokens set follows the decision, as for the axum layer’s AuthLayerBuilder::build_with_decision: kept, and merged with the decision’s token, when the decision carries one (StaticOnly/StaticAndOAuth); dropped with it on StaticIgnored (accept_static_bearer: false wins); refused alongside OAuthOnly or Unauthenticated, which were decided without any static token.

§Errors

AuthLayerError::DecisionNeedsOAuth when the decision was made with OAuth on and no validator was given, AuthLayerError::DecisionWithoutOAuth when it was made with OAuth off (including Unauthenticated) and one was given, then AuthLayerError::DecisionWithoutStaticToken for a non-empty static_tokens set with an OAuthOnly or Unauthenticated decision, then AuthLayerError::ScopesWithoutAuthentication for require_scopes with an Unauthenticated decision. Otherwise as HttpAuthLayerBuilder::build.

Source

pub fn build(self) -> Result<HttpAuthLayer<R>, AuthLayerError>

Build the layer.

§Errors

AuthLayerError::NoCredential with neither a non-blank static token (from static_token or a non-empty static_tokens set) nor an OAuth validator; AuthLayerError::NoSources with an empty source list; AuthLayerError::InvalidChallenge when the validator’s challenge is not a valid header value (only reachable from a hand-edited resolved config); AuthLayerError::InvalidScope when a require_scopes entry is not a scope-token; AuthLayerError::ScopesNeedOAuth for require_scopes with no OAuth validator and no static_token_bypasses_scopes.

Trait Implementations§

Source§

impl<R> Debug for HttpAuthLayerBuilder<R>

Hand-written so the static token never reaches a log line through {:?}.

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Default for HttpAuthLayerBuilder

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

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

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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