Skip to main content

RefreshTokenRecord

Struct RefreshTokenRecord 

Source
#[non_exhaustive]
pub struct RefreshTokenRecord {
Show 13 fields pub refresh_token: String, pub client_id: ClientId, pub subject: Option<String>, pub scope: ScopeSet, pub resource: Vec<String>, pub authorization_details: AuthorizationDetails, pub grant_established_at: SystemTime, pub expires_at: Option<SystemTime>, pub jkt: Option<Box<str>>, pub x5t_s256: Option<Box<CertificateThumbprint>>, pub family_id: String, pub state: RefreshTokenState, pub authentication: Option<Box<Authentication>>,
}
Expand description

A persisted refresh token. Single use: redemption goes through crate::store::Storage::take_refresh_token, and rotation issues a replacement carrying the SAME expires_at, so a chain has an absolute lifetime rather than a sliding one.

Debug is hand-written (see below) rather than derived: refresh_token is a bearer credential whose leak is exactly the compromise RFC 9700 section 4.14.2 defends against, so it must not reach a host’s logs through {:?}. #[non_exhaustive], on the same four features and the same storage argument as IssuedToken: a Storage implementor round-trips this through serde and never names a field, and anyone assembling one by hand goes through RefreshTokenRecord::new.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§refresh_token: String

The opaque refresh token string (the storage key).

§client_id: ClientId

The client the token was issued to; presentation by any other client is invalid_grant and leaves the record untouched.

§subject: Option<String>

The resource owner the chain acts for.

§scope: ScopeSet

The scope originally granted; refreshes may narrow, never widen.

§resource: Vec<String>

The RFC 8707 resource indicators originally granted. Carried across rotation for the same reason scope is: section 2 lets a token request narrow the set and never widen it, so the chain has to remember what it started with. Empty when the grant named none.

§authorization_details: AuthorizationDetails
Available on crate feature rar only.

The RFC 9396 authorization details originally granted. Carried across rotation for the same reason scope and resource are: section 6 lets a token request narrow the set and never widen it, so the chain has to remember what it started with, and a rotation that narrowed must not be climbable back on the next one.

#[serde(default)], which IssuedToken::authorization_details states in full: a chain written by a build without rar carries no such key, this is not an Option so serde supplies no default of its own, and without one every live chain becomes unreadable the moment anything in the host’s dependency graph turns the feature on.

§grant_established_at: SystemTime

The instant the GRANT behind this chain was authorized, CARRIED ACROSS ROTATION and never restamped, for the same reason scope and resource are carried: the chain has to remember the one decision that started it. Restamping it on each rotation would let a chain walk forward past a revocation it was supposed to die to.

See IssuedToken::grant_established_at, which this is copied into on every rotation, and which states in full why the serde default below is the epoch: a chain a 0.9.0 node wrote carries no such key, and the epoch is the reading that a standing revocation REFUSES rather than the one it admits.

§expires_at: Option<SystemTime>

Absolute chain expiry; None means the chain does not expire by time.

On a Spent record this doubles as the RETENTION deadline: a spent token is kept only so that its reuse can be recognised, and a chain with no absolute expiry would otherwise keep every superseded link forever. The server therefore stamps a spent record from a never-expiring chain with now + ServerConfig::refresh_reuse_window, which is what makes crate::store::Storage::sweep_expired able to reclaim it.

§jkt: Option<Box<str>>
Available on crate feature dpop only.

RFC 9449 section 5: the RFC 7638 thumbprint of the DPoP key this refresh chain is bound to, or None for an unbound chain.

Carried across rotation and CHECKED on redemption. Without it the binding would be decorative for anything but the first access token: a stolen refresh token could simply be re-bound to the thief’s key on the next rotation, leaving the attacker holding a token they can prove possession for and the victim’s key the one that gets refused.

§x5t_s256: Option<Box<CertificateThumbprint>>
Available on crate feature mtls only.

RFC 8705 section 3: the client certificate this refresh chain is bound to, or None for an unbound chain.

Carried across rotation and CHECKED on redemption, exactly as jkt is and for the same argument: without it the binding would be decorative past the first access token, because a stolen refresh token could simply be re-bound to whatever certificate the thief holds on the next rotation. Section 3 makes this a MUST for public clients specifically; this crate applies it to every chain that was issued over a certificate, because a chain whose holder proved possession of a key once should have to keep proving it, and for a confidential mutual-TLS client the rule costs nothing (it presents that certificate on every request anyway).

§family_id: String

The FAMILY this token belongs to: one identifier shared by every token, access or refresh, minted from the same authorization grant, and carried across rotation unchanged.

This is what makes RFC 9700 section 4.14.2 implementable at all. Without it the AS can refuse a reused token but cannot reach the tokens the thief already rotated into, which is the defence exactly inverted: the victim is locked out and the attacker is not.

§state: RefreshTokenState

Whether this link is still redeemable, or is a retained rotated one.

§authentication: Option<Box<Authentication>>
Available on crate feature consent only.

The authentication the host reported when the grant this chain came from was approved, carried across rotation UNCHANGED.

Carried rather than restamped because a rotation is not a new authentication: the user is not present, nothing has been proven again, and giving a refreshed token a fresh auth_time would let any client defeat an RFC 9470 max_age by refreshing. See IssuedToken::authentication for why it is boxed.

Implementations§

Source§

impl RefreshTokenRecord

Source

pub fn new( refresh_token: impl Into<String>, client_id: ClientId, subject: Option<String>, scope: ScopeSet, family_id: impl Into<String>, ) -> Self

A LIVE link: state is RefreshTokenState::Active, because a record nobody has rotated yet is the only kind worth minting, and a caller building a spent one for a reuse test assigns the field afterwards rather than passing a flag that is Active every real time.

family_id is an argument and not a default, unlike almost everything else here, because there is no honest default for it: an invented one would put this chain in a family of its own and quietly cost RFC 9700 section 4.14.2 the access tokens minted alongside it, which is the failure the field exists to prevent. expires_at starts None, a chain with no absolute lifetime, which is what crate::server::ServerConfig produces when the host has set no refresh TTL.

Trait Implementations§

Source§

impl Clone for RefreshTokenRecord

Source§

fn clone(&self) -> RefreshTokenRecord

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 RefreshTokenRecord

Hand-written so the opaque refresh_token never prints. EVERY other field prints, on the rule IssuedToken’s Debug states in full: state and family_id are precisely what an operator debugging an RFC 9700 section 4.14.2 family revocation needs to see, grant_established_at is what a crate::store::RevocationBarrier is compared against, and none of the three is a credential.

Source§

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

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

impl<'de> Deserialize<'de> for RefreshTokenRecord

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for RefreshTokenRecord

Source§

impl PartialEq for RefreshTokenRecord

Source§

fn eq(&self, other: &RefreshTokenRecord) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for RefreshTokenRecord

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for RefreshTokenRecord

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> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

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

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.