Skip to main content

StaticTokens

Struct StaticTokens 

Source
pub struct StaticTokens { /* private fields */ }
Expand description

A set of static API keys, each with an optional label: several keys accepted at once, for rotating a key with no downtime (the old and the new one both accepted until every client has moved) or for one key per client.

Opaque: nothing reads a secret back out of it. Build it with StaticTokens::single or StaticTokens::new and StaticTokens::with; hand it to authenticate_with_static_tokens, or to a layer’s static_tokens builder method (features axum/tower). The env feature’s static_tokens_from_env loads a current and a next key.

§Rules

Checked by StaticTokens::with, which refuses (never silently fixes) an entry that breaks one:

  • A secret must not be empty or whitespace-only (StaticTokensError::BlankSecret). It is otherwise kept verbatim, untrimmed, like the single static_token everywhere else.
  • A secret must not repeat one already in the set (StaticTokensError::DuplicateSecret): two entries holding one secret would make the reported label depend on insertion order, and for per-client keys means two clients share a key, so it is refused rather than deduplicated.
  • A label is 1 to StaticTokens::MAX_LABEL_LEN (64) visible ASCII characters, ! through ~ — no space, no control character, nothing else (StaticTokensError::InvalidLabel) — so it is always safe to put in a log line or a metric. Labels are not secrets.
  • A label must not repeat one already in the set (StaticTokensError::DuplicateLabel); any number of entries may be unlabeled.

§Security

Comparison is constant-time across entries: every candidate is compared with every entry, with no early exit once one matches, and the matching index is selected without a data-dependent branch (subtle). What is not hidden is length: each single comparison returns faster when the candidate’s length differs from that entry’s, exactly as the single-token comparison always has, so the total time depends on how many entries share the candidate’s length (never on which entry matched, apart from cloning the matched label after the comparison, observable only by a holder of a valid key). Use high-entropy keys of one fixed length, and the length reveals nothing useful.

Debug prints the number of entries and their labels, never a secret. No PartialEq: comparing two sets would compare secrets in variable time. Each secret is held in a zeroize::Zeroizing buffer, wiped when the set (or a clone of it) is dropped; so are the layer builders’ single static_token, and the env loaders’ intermediate copies (untrimmed values, a next key equal to the current one). What is not wiped: the strings a caller passes in or keeps (with’s argument before it is moved in, secret_from_env’s returned String), a StaticTokenDecision’s String payload, the process environment, and a secrets file. A String’s earlier buffers, left behind if it was grown before being handed over, are not wiped either.

§Examples

use oauth_resource_server::{StaticTokens, StaticTokensError};

// Rotation: the current key and the one replacing it.
let tokens = StaticTokens::new()
    .with(Some("current"), "example-key-2024")?
    .with(Some("next"), "example-key-2025")?;
assert_eq!(tokens.len(), 2);
assert!(!format!("{tokens:?}").contains("example-key"));

// Blank secrets and repeated secrets are refused.
assert!(matches!(
    StaticTokens::single("  "),
    Err(StaticTokensError::BlankSecret { .. })
));
assert!(matches!(
    tokens.with(None, "example-key-2025"),
    Err(StaticTokensError::DuplicateSecret { .. })
));

Implementations§

Source§

impl StaticTokens

Source

pub const MAX_LABEL_LEN: usize = 64

The longest label StaticTokens::with accepts, in bytes (every accepted character is one byte).

Source

pub fn new() -> Self

An empty set. Add entries with StaticTokens::with. An empty set accepts nothing and, handed to a layer, does not count as a credential mechanism.

Source

pub fn single(secret: impl Into<String>) -> Result<Self, StaticTokensError>

A set holding one unlabeled secret: the same credential a single static_token configures.

§Errors

StaticTokensError::BlankSecret for an empty or whitespace-only secret.

Source

pub fn with( self, label: Option<&str>, secret: impl Into<String>, ) -> Result<Self, StaticTokensError>

This set plus one more secret, optionally labeled (see the rules).

§Errors

StaticTokensError::BlankSecret, StaticTokensError::InvalidLabel, StaticTokensError::DuplicateLabel or StaticTokensError::DuplicateSecret, checked in that order. The set is consumed either way; on an error its secrets are wiped with it.

Source

pub fn len(&self) -> usize

How many secrets the set holds.

Source

pub fn is_empty(&self) -> bool

Whether the set holds none.

Source

pub fn labels(&self) -> impl Iterator<Item = Option<&str>>

Every entry’s label, in insertion order (None for an unlabeled one) — for a startup log line saying which keys are accepted.

Trait Implementations§

Source§

impl Clone for StaticTokens

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 Debug for StaticTokens

Hand-written so no secret ever reaches a log line through {:?}: the count and the labels (log-safe by construction), never a secret.

Source§

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

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

impl Default for StaticTokens

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