oauth_as/token_exchange.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (C) 2026 Matthew Jackson
3
4//! RFC 8693 token exchange: `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`.
5//!
6//! One client presents a token it holds and receives another one. That is the basis of nearly
7//! every service-to-service call in a system with more than one hop: a gateway that received a
8//! user's token needs a token it can send onward without handing the downstream service the
9//! original credential, and without either service having to invent its own scheme for saying who
10//! is really calling.
11//!
12//! # Delegation and impersonation, and how to tell which you got
13//!
14//! RFC 8693 section 1.1 draws the distinction and this module makes it observable rather than
15//! implicit, because it is the whole security meaning of the call:
16//!
17//! - IMPERSONATION. No `actor_token`. The issued token names the subject and nothing else, so the
18//! downstream resource cannot tell the caller apart from the original principal "within some
19//! defined rights context" (section 1.1). [`ExchangeSemantics::Impersonation`].
20//! - DELEGATION. An `actor_token` is presented, and it authenticates the acting party. The issued
21//! token still names the subject, but it also carries the section 4.1 `act` claim identifying
22//! who is acting for them, so the resource can log and authorize "A on behalf of B" rather than
23//! "B". [`ExchangeSemantics::Delegation`], with [`ExchangedToken::act`] populated.
24//!
25//! [`ExchangedToken`] tells the host which of the two it just produced. It is not a detail to be
26//! inferred from whether a parameter was sent.
27//!
28//! # The ceiling, which is the entire risk of this grant
29//!
30//! An exchange must never produce a token that can do something the subject token could not.
31//! Widening is the only interesting attack on token exchange: a client that legitimately holds a
32//! read-only token for one resource asks for a write token for another, and if the AS obliges,
33//! every access control upstream of it has been bypassed by one HTTP request.
34//!
35//! This crate already had the right pattern in two places, and this grant reuses THE SAME CODE
36//! rather than a similar-looking copy of it (the crate-private `narrow_resources` and
37//! `validate_resources` on `AuthorizationServer`, which the authorization code and refresh paths
38//! call too; see the notes below):
39//!
40//! - SCOPE. RFC 6749 section 6 lets a refresh narrow and never widen; the same rule applies here,
41//! against the subject token's granted scope. A widening request is `invalid_scope`.
42//! - RESOURCE / AUDIENCE. RFC 8707 section 2 lets a token request narrow the audience the grant
43//! obtained and never widen it. The subject token's recorded `resource` list is the ceiling, and
44//! a target outside it is `invalid_target`, which is the code RFC 8693 section 2.2.2 names for
45//! exactly this. A subject token that named NO resource has nothing to narrow, so naming one is
46//! widening from nothing and is refused, exactly as it is for the authorization code grant.
47//!
48//! A SECOND ceiling applies on top, which RFC 8693 does not require and this crate imposes anyway:
49//! the issued token is issued TO the exchanging client, so its scope must also sit inside that
50//! client's own registration ([`crate::client::Client::allowed_scopes`]). Without it, a client
51//! registered for `read` could hold someone else's `write` token for a minute and mint itself a
52//! `write` token of its own, which is the registration boundary defeated by a bearer string.
53//!
54//! # A SENDER-CONSTRAINED subject token is REFUSED, and this is the argued part
55//!
56//! RFC 9449 (DPoP) and RFC 8705 (mutual TLS) exist to buy ONE property: a token that leaks is worth
57//! nothing to whoever finds it, because spending it needs a private key or a client certificate
58//! that never left the legitimate client. An exchange takes a token STRING and returns a different
59//! token. If it ignores the subject token's binding, then anyone able to authenticate as any client
60//! registered for this grant, an insider, a compromised service, a leaked client secret, posts a
61//! stolen bound token and receives a plain bearer token with the same subject, scope and audience.
62//! The binding is gone and the theft is spendable. That is the exact property the deployment paid
63//! for, defeated by one request.
64//!
65//! RFC 8693 does not settle this, so the choice is stated rather than assumed. Three candidates:
66//!
67//! - DOWNGRADE SILENTLY, which is what this crate did through 0.9.0 and is the one answer that is
68//! definitely wrong: the operator who turned DPoP on has no way to learn that a grant they also
69//! turned on removes it again.
70//! - PROPAGATE the binding, so the issued token inherits the subject token's `cnf`. Attractive and
71//! still wrong here: the new token is issued TO THE EXCHANGING CLIENT, which does not hold the
72//! original client's key. The result is a token no conforming resource server would let its
73//! holder use (RFC 9449 section 7.1 requires a proof under the bound key on every request), which
74//! is a broken grant dressed as a secure one. Worse, it reads at a glance as though possession
75//! was proven, when nobody proved anything: the exchanging client presented a bearer string.
76//! - REFUSE, which is what this crate does. A sender-constrained subject token is "unacceptable
77//! based on policy" in the sense of RFC 8693 section 2.2.2, and the section names `invalid_request`
78//! for exactly that. The refusal is loud, it happens at the AS rather than at some resource server
79//! later, and it cannot be mistaken for success.
80//!
81//! What would make an exchange of a bound token safe is the exchanging client PROVING possession of
82//! the key the subject token is bound to, which is the RFC 9449 section 7 check a resource server
83//! performs. That is a real design and it is deliberately not faked here: [`TokenExchangeRequest`]
84//! carries no proof and no certificate, so there is nothing to check, and a seam that accepted one
85//! would also have to bind the ISSUED token to the same key, at which point the exchange is a
86//! delegation between two holders of one key rather than between two clients. If a deployment needs
87//! that, it is an additive change to this request type and to the refusal below; it is not
88//! something to approximate by waving the check through.
89//!
90//! # What this grant does NOT do, stated rather than left to be discovered
91//!
92//! - No refresh token is issued. RFC 8693 section 2.2.1 says one "will typically not be issued
93//! when the exchange is of one temporary credential for a different temporary credential", and
94//! issuing one would let an exchanged token outlive by rotation the grant it was derived from.
95//! - The issued token's lifetime is the LESSER of
96//! [`crate::server::ServerConfig::access_token_ttl`] and the subject token's own remaining
97//! lifetime. Through 0.9.0 it was the former alone, and that contradicted the bullet immediately
98//! above: the exchanged token is an ordinary access token, so it is an acceptable subject token
99//! in its turn, and self-exchange is permitted. A client could therefore re-exchange just before
100//! each expiry and receive a fresh full TTL every time, renewing by exchange exactly the grant
101//! lifetime that withholding a refresh token was meant to bound. Clamping makes time behave the
102//! way scope, audience and RFC 9396 details already do here: an exchange may narrow, never widen.
103//! - The `act` claim reaches a resource server BY BOTH ROUTES as of 0.9.1: it is persisted on
104//! [`crate::token::IssuedToken`] and reported by RFC 7662 introspection
105//! ([`crate::token::IntrospectionResponse::act`]), and under the `jwt` feature it is an RFC 9068
106//! claim in the signed access token ([`crate::jwt::AccessTokenClaims::act`]). This paragraph
107//! described a GAP through 0.9.0 and is kept as the record of why it closed when it did.
108//!
109//! BOTH ROUTES ARE NEEDED, which an earlier draft of this paragraph got wrong by claiming the
110//! record alone had closed it. The two token formats reach a resource server differently: an
111//! OPAQUE token carries nothing, so introspection is the only channel it has, while a JWT is
112//! typically validated offline and introspected never. Persisting the claim and stopping there
113//! would have moved the deficiency from one deployment shape to the other rather than ending it.
114//!
115//! Two things stood in the way and both are spent. The first was allocation, on the reasoning
116//! that [`crate::token::IssuedToken`] "is cloned on every token-plane request":
117//! [`crate::store::Storage::get_token`] returns an `Arc<IssuedToken>` now, so the record's shape
118//! costs a read nothing, and the field costs a deployment without this feature zero bytes and
119//! one with it 8 bytes per token plus one allocation per DELEGATED token.
120//!
121//! The second was the real one, the PERSISTENCE CONTRACT: `IssuedToken` is the record every
122//! host's [`crate::store::Storage`] implementation writes and reads, so a new field is a
123//! migration in stores this crate does not own rather than a struct edit here. That is a
124//! coordinated change with a release behind it, and 0.9.1 is that release: it is already
125//! breaking `Storage` for the revocation-barrier rule, so a host migrates once instead of twice.
126//!
127//! Why it was worth doing rather than deferring again: this crate's default access token is
128//! OPAQUE, so introspection is the ONLY channel a resource server has. A delegation it cannot
129//! see is a delegation the resource server has to take the host's word for, which collapses RFC
130//! 8693 section 1.1 delegation back into impersonation from the one viewpoint the distinction
131//! exists for. The claim was persisted before there was anybody to read it, because the record
132//! is the half that cannot be added later; since 0.9.2 a resource server registered in
133//! `ServerConfig::resource_servers` is served on the opaque route too, so that viewpoint is now
134//! a real one rather than a promised one.
135
136use std::fmt;
137use std::str::FromStr;
138
139use serde::{Deserialize, Serialize};
140
141use crate::client::ClientId;
142use crate::error::{ErrorCode, ErrorResponse};
143use crate::events::Event;
144use crate::grant::GrantType;
145use crate::scope::ScopeSet;
146use crate::server::{AuthorizationServer, Bound, ClientCredential, Clock};
147use crate::store::{Storage, StorageError};
148
149/// The largest number of RFC 8693 section 2.1.1 `audience` values one exchange may carry.
150///
151/// Deliberately the SAME number as [`crate::server::MAX_RESOURCE_INDICATORS`], and not a second
152/// judgement: section 2.1.1 says `audience` and `resource` name the same thing in two spellings,
153/// one a logical name and one a URI, and this crate funnels both into one list against one ceiling.
154/// Two constants would be two numbers a reader has to keep in step, for a distinction the RFC
155/// itself does not draw.
156///
157/// The reason for a bound at all is the reason given on [`crate::server::MAX_RESOURCE_INDICATORS`]:
158/// the parameter is repeatable and the dedup is an O(n) scan per element, so `n` is chosen by the
159/// caller. This endpoint IS authenticated, which is why the finding is a rung lower, but a client
160/// that has merely leaked its secret should not get an amplifier along with it.
161pub const MAX_AUDIENCE_VALUES: usize = crate::server::MAX_RESOURCE_INDICATORS;
162
163/// The longest RFC 8693 section 4.1 `act` chain this server will mint: the number of ACTORS the
164/// nested claim may name, counting the current one.
165///
166/// A bound is required rather than tidy. Each delegation exchange nests the subject token's own
167/// `act` inside the new one, and the result is PERSISTED on [`crate::token::IssuedToken`] and
168/// serialized into every RFC 9068 access token and RFC 7662 introspection response the token
169/// produces. Exchanging one's own token is explicitly permitted, so without a bound an
170/// authenticated client can loop — exchange, then exchange the result — and each hop adds a link
171/// that every later read pays for, in the store and on the wire. That is a client choosing how much
172/// storage the server spends, which is the same shape as the repeatable-parameter bounds above.
173///
174/// Eight is chosen against real topologies rather than against the RFC, which sets no limit:
175/// section 1.1's delegation is a call graph, and a request that has legitimately crossed eight
176/// distinct delegating services has a shape an operator should be told about rather than one this
177/// server should quietly extend. A chain at the bound is refused with section 2.2.2's
178/// `invalid_request` rather than TRUNCATED, because truncation would silently discard exactly the
179/// audit history the nesting exists to keep, and a server that quietly forgets who acted earlier is
180/// worse than one that says it will not go further.
181pub const MAX_ACT_CHAIN_DEPTH: usize = 8;
182
183use crate::token::TokenType;
184
185pub use crate::grant::TOKEN_EXCHANGE_GRANT_URN;
186
187/// The RFC 8693 section 3 token type identifiers.
188///
189/// The URNs are the wire values, verbatim. This is a closed enum rather than a string because the
190/// `subject_token_type` is a security-relevant statement about how the presented string must be
191/// checked, and "a type identifier this server did not recognise" has to be a REFUSAL rather than
192/// something that falls through to a default (RFC 8693 section 2.2.2: a subject token that is
193/// unacceptable based on policy is an error).
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
195pub enum TokenTypeIdentifier {
196 /// `urn:ietf:params:oauth:token-type:access_token`. The only type this server accepts as a
197 /// subject or actor token, and the only type it issues.
198 #[serde(rename = "urn:ietf:params:oauth:token-type:access_token")]
199 AccessToken,
200 /// `urn:ietf:params:oauth:token-type:refresh_token`.
201 #[serde(rename = "urn:ietf:params:oauth:token-type:refresh_token")]
202 RefreshToken,
203 /// `urn:ietf:params:oauth:token-type:id_token`.
204 #[serde(rename = "urn:ietf:params:oauth:token-type:id_token")]
205 IdToken,
206 /// `urn:ietf:params:oauth:token-type:saml1`.
207 #[serde(rename = "urn:ietf:params:oauth:token-type:saml1")]
208 Saml1,
209 /// `urn:ietf:params:oauth:token-type:saml2`.
210 #[serde(rename = "urn:ietf:params:oauth:token-type:saml2")]
211 Saml2,
212 /// `urn:ietf:params:oauth:token-type:jwt`. Note that RFC 8693 section 3 defines this as "any
213 /// JWT", which is a statement about ENCODING and not about who issued it; this server does not
214 /// accept it, because accepting a JWT as a subject token means deciding whose signature to
215 /// trust, and that is a policy no library can pick on a host's behalf.
216 #[serde(rename = "urn:ietf:params:oauth:token-type:jwt")]
217 Jwt,
218}
219
220impl TokenTypeIdentifier {
221 /// Resolve a wire `*_token_type` value WITHOUT allocating, returning `None` for anything RFC
222 /// 8693 section 3 does not register.
223 ///
224 /// This is the parse the HTTP surface uses, for the reason [`crate::grant::GrantType::parse`]
225 /// exists: [`FromStr`]'s error carries the caller's value, and the router deliberately does not
226 /// echo it (RFC 6749 s5.2 restricts `error_description` to a charset an attacker-supplied URN
227 /// need not respect), so the copy was allocated and dropped unread. The refusal STRING here was
228 /// already made a `&'static str` for exactly this rule; the allocation underneath it was
229 /// missed, and it is the worse one, because the caller chooses its SIZE and this refusal
230 /// happens before the presented client credential has been checked.
231 ///
232 /// [`FromStr`] is unchanged and still carries the value, for the host-side callers that want
233 /// to report which URN they got wrong.
234 pub fn parse(s: &str) -> Option<Self> {
235 match s {
236 "urn:ietf:params:oauth:token-type:access_token" => {
237 Some(TokenTypeIdentifier::AccessToken)
238 }
239 "urn:ietf:params:oauth:token-type:refresh_token" => {
240 Some(TokenTypeIdentifier::RefreshToken)
241 }
242 "urn:ietf:params:oauth:token-type:id_token" => Some(TokenTypeIdentifier::IdToken),
243 "urn:ietf:params:oauth:token-type:saml1" => Some(TokenTypeIdentifier::Saml1),
244 "urn:ietf:params:oauth:token-type:saml2" => Some(TokenTypeIdentifier::Saml2),
245 "urn:ietf:params:oauth:token-type:jwt" => Some(TokenTypeIdentifier::Jwt),
246 _ => None,
247 }
248 }
249
250 /// The registered URN, verbatim.
251 pub fn as_str(self) -> &'static str {
252 match self {
253 TokenTypeIdentifier::AccessToken => "urn:ietf:params:oauth:token-type:access_token",
254 TokenTypeIdentifier::RefreshToken => "urn:ietf:params:oauth:token-type:refresh_token",
255 TokenTypeIdentifier::IdToken => "urn:ietf:params:oauth:token-type:id_token",
256 TokenTypeIdentifier::Saml1 => "urn:ietf:params:oauth:token-type:saml1",
257 TokenTypeIdentifier::Saml2 => "urn:ietf:params:oauth:token-type:saml2",
258 TokenTypeIdentifier::Jwt => "urn:ietf:params:oauth:token-type:jwt",
259 }
260 }
261}
262
263impl fmt::Display for TokenTypeIdentifier {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 f.write_str(self.as_str())
266 }
267}
268
269/// The rejection for a `*_token_type` value that is not one RFC 8693 section 3 registers.
270///
271/// The payload is SEALED and read through [`UnknownTokenTypeIdentifier::identifier`], matching
272/// [`crate::par::RequestObjectKeyError`], which is the crate's other one-payload rejection type.
273/// The two disagreed: this one published its `String` as a tuple field, so a host could also
274/// CONSTRUCT one and hand it to code that reasonably assumed only this crate mints them. Readable
275/// and not forgeable is the rule both follow now.
276#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct UnknownTokenTypeIdentifier(String);
278
279impl UnknownTokenTypeIdentifier {
280 /// The unregistered identifier, exactly as it arrived.
281 ///
282 /// Echoing it back is safe and useful: it is a `*_token_type` URN out of the request, not a
283 /// token, and a host debugging an interoperability failure needs to see what the peer sent.
284 pub fn identifier(&self) -> &str {
285 &self.0
286 }
287}
288
289impl fmt::Display for UnknownTokenTypeIdentifier {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 write!(f, "unknown token type identifier {:?}", self.0)
292 }
293}
294
295impl std::error::Error for UnknownTokenTypeIdentifier {}
296
297impl FromStr for TokenTypeIdentifier {
298 type Err = UnknownTokenTypeIdentifier;
299
300 fn from_str(s: &str) -> Result<Self, Self::Err> {
301 TokenTypeIdentifier::parse(s).ok_or_else(|| UnknownTokenTypeIdentifier(s.to_string()))
302 }
303}
304
305/// Which of RFC 8693 section 1.1's two semantics an exchange produced.
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub enum ExchangeSemantics {
308 /// No acting party was presented: the issued token is indistinguishable from one the subject
309 /// holds directly, within the rights context it covers.
310 Impersonation,
311 /// An acting party authenticated and is named in [`ExchangedToken::act`]: the issued token
312 /// says "the actor, acting for the subject".
313 Delegation,
314}
315
316/// The RFC 8693 section 4.1 `act` (actor) claim: who authority was delegated TO.
317///
318/// Section 4.1 is explicit about what a consumer may do with it: "the consumer of a token MUST
319/// only consider the token's top-level claims and the party identified as the current actor by the
320/// act claim", and a nested `act` is a PRIOR actor, kept for audit rather than for authorization.
321/// That is why [`ActClaim::act`] is boxed and optional rather than a flat list: the nesting IS the
322/// ordering, and flattening it would lose which actor is current.
323#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
324pub struct ActClaim {
325 /// The acting party's subject identifier.
326 pub sub: String,
327 /// RFC 8693 section 4.3 `client_id`, when the actor is (or authenticated as) a client. Present
328 /// here because for a machine-to-machine actor the client identifier is frequently the only
329 /// identity there is, and section 4.1's example carries it.
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub client_id: Option<String>,
332 /// A PRIOR actor in the delegation chain (section 4.1). The outermost claim is the current
333 /// actor; anything nested inside acted earlier.
334 ///
335 /// BOXED because the type is otherwise infinitely sized, and because the overwhelmingly common
336 /// chain is one link long and should not carry the storage for more.
337 #[serde(skip_serializing_if = "Option::is_none")]
338 pub act: Option<Box<ActClaim>>,
339}
340
341/// An RFC 8693 section 2.1 token exchange request, as the host parsed it off the wire.
342///
343/// Borrowed rather than owned: a token-endpoint request is parsed, judged and dropped, and the
344/// three credentials in here (`client_secret`, `subject_token`, `actor_token`) are strings a host
345/// should be copying as little as possible.
346///
347/// `Debug` is hand-written (see below) rather than derived, for the reason
348/// [`crate::server::TokenRequest`] gives: every one of those three is a bearer credential, and this
349/// is precisely the value a host is most likely to debug-print, because it is the request it just
350/// parsed.
351#[derive(Clone, PartialEq, Eq)]
352/// `#[non_exhaustive]`: the two `client-assertion` fields appear only under that feature, so a host
353/// that writes this as a literal is writing one of two different structs and does not get to choose
354/// which. [`TokenExchangeRequest::new`] already exists and already says the rest is "optional and
355/// set on the returned value"; the attribute is what makes that the only way in, rather than the
356/// documented way in that a literal quietly bypasses.
357#[non_exhaustive]
358pub struct TokenExchangeRequest<'a> {
359 /// The client performing the exchange. It is authenticated, and it is the client the issued
360 /// token belongs to.
361 pub client_id: &'a ClientId,
362 /// Its secret, for a client registered for `client_secret_basic` or `client_secret_post`.
363 /// A confidential credential of SOME kind is required: see [`TokenExchange::exchange_token`]
364 /// on why a public client may not use this grant.
365 pub client_secret: Option<&'a str>,
366 /// RFC 7521 section 4.2 `client_assertion_type`, for a client registered for
367 /// `private_key_jwt` or `client_secret_jwt`.
368 ///
369 /// This field and the one below exist because RFC 8693 section 2.1 authenticates the client
370 /// "as described in Section 2.3 of \[RFC6749\]", which is a reference to every method the server
371 /// offers and not to shared secrets alone. Carrying only `client_secret` meant a confidential
372 /// client registered for assertion authentication was answered `invalid_client` on a grant the
373 /// RFC 8414 document advertises to it: the credential it presented had nowhere to travel.
374 #[cfg(feature = "client-assertion")]
375 pub client_assertion_type: Option<&'a str>,
376 /// RFC 7523 section 2.2 `client-assertion`: the signed JWT itself.
377 #[cfg(feature = "client-assertion")]
378 pub client_assertion: Option<&'a str>,
379 /// REQUIRED (section 2.1). The token representing the party on whose behalf the request is
380 /// made.
381 pub subject_token: &'a str,
382 /// REQUIRED (section 2.1). The type of `subject_token`.
383 pub subject_token_type: TokenTypeIdentifier,
384 /// OPTIONAL (section 2.1). The token representing the ACTING party. Its presence is what makes
385 /// the exchange delegation rather than impersonation.
386 pub actor_token: Option<&'a str>,
387 /// REQUIRED when `actor_token` is present, and meaningless without it (section 2.1).
388 pub actor_token_type: Option<TokenTypeIdentifier>,
389 /// OPTIONAL (section 2.1). RFC 8707 resource indicators for the target service. May only
390 /// narrow what the subject token already carries.
391 pub resource: &'a [String],
392 /// OPTIONAL (section 2.1). The logical name of the target service.
393 ///
394 /// Section 2.1.1 relates `audience` and `resource`: both name where the client intends to use
395 /// the token, differing in whether the name is a URI. This crate has exactly ONE audience
396 /// model, the RFC 8707 resource indicator recorded on the grant, so an `audience` value is
397 /// checked against the same ceiling as a `resource` value. A logical name the subject token
398 /// does not already carry can therefore never be granted, and answers `invalid_target`.
399 pub audience: &'a [String],
400 /// OPTIONAL (section 2.1). Narrows the issued token's scope; never widens it.
401 pub scope: Option<&'a ScopeSet>,
402 /// OPTIONAL (section 2.1). Absent means the server's own choice, which for this server is
403 /// always an access token.
404 pub requested_token_type: Option<TokenTypeIdentifier>,
405}
406
407impl<'a> TokenExchangeRequest<'a> {
408 /// The minimum request RFC 8693 section 2.1 admits: a client, the subject token it is
409 /// exchanging, and WHAT THE CALLER SAYS THAT TOKEN IS. Everything else is optional and set on
410 /// the returned value.
411 ///
412 /// `subject_token_type` is an argument rather than a default because it is the parameter the
413 /// exchange refuses on (see [`TokenExchange::exchange_token`]): a caller that presents a refresh
414 /// token and labels it an access token is asking this server to check the string a different
415 /// way than it is going to, and refusing the mismatch is what stops the type parameter being
416 /// decorative. Section 2.1 makes it REQUIRED. Defaulting it here meant a host that assembled
417 /// the request in code, and forgot to copy the form field across, silently converted that
418 /// refusal into a pass, because the value the constructor invented is precisely the one value
419 /// that passes.
420 pub fn new(
421 client_id: &'a ClientId,
422 subject_token: &'a str,
423 subject_token_type: TokenTypeIdentifier,
424 ) -> Self {
425 TokenExchangeRequest {
426 client_id,
427 client_secret: None,
428 #[cfg(feature = "client-assertion")]
429 client_assertion_type: None,
430 #[cfg(feature = "client-assertion")]
431 client_assertion: None,
432 subject_token,
433 subject_token_type,
434 actor_token: None,
435 actor_token_type: None,
436 resource: &[],
437 audience: &[],
438 scope: None,
439 requested_token_type: None,
440 }
441 }
442}
443
444/// Hand-written so no credential reaches a debug format, while everything that identifies WHICH
445/// exchange this is stays visible. The Some/None distinction is kept for the redacted fields
446/// because it is not a credential: it is the difference between an impersonation request and a
447/// delegation request, which is the first thing anyone debugging this grant needs to see.
448impl fmt::Debug for TokenExchangeRequest<'_> {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 fn redact_opt<T>(value: &Option<T>) -> Option<&'static str> {
451 value.as_ref().map(|_| "[redacted]")
452 }
453 let mut out = f.debug_struct("TokenExchangeRequest");
454 out.field("client_id", &self.client_id)
455 .field("client_secret", &redact_opt(&self.client_secret));
456 // The assertion is a bearer credential for as long as it is unexpired (RFC 7523 s3), so it
457 // is redacted exactly as the secret is. The TYPE is printed, because it is not a
458 // credential (it is a fixed URN) and a mistyped one is the commonest way this
459 // authentication method fails.
460 #[cfg(feature = "client-assertion")]
461 out.field("client_assertion_type", &self.client_assertion_type)
462 .field("client_assertion", &redact_opt(&self.client_assertion));
463 out.field("subject_token", &"[redacted]")
464 .field("subject_token_type", &self.subject_token_type)
465 .field("actor_token", &redact_opt(&self.actor_token))
466 .field("actor_token_type", &self.actor_token_type)
467 .field("resource", &self.resource)
468 .field("audience", &self.audience)
469 .field("scope", &self.scope)
470 .field("requested_token_type", &self.requested_token_type)
471 .finish()
472 }
473}
474
475/// The RFC 8693 section 2.2.1 successful response.
476///
477/// Deliberately NOT [`crate::token::TokenResponse`]: section 2.2.1 adds `issued_token_type` as
478/// REQUIRED, and the meaning of `access_token` widens to "the security token issued", which need
479/// not be an OAuth access token at all. Reusing the RFC 6749 section 5.1 type would have made
480/// `issued_token_type` optional in the type system, and a REQUIRED member that the type lets you
481/// forget is a member that eventually goes missing.
482///
483/// `Debug` is hand-written so the issued token does not print, matching every other credential
484/// carrying type in this crate.
485#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
486pub struct TokenExchangeResponse {
487 /// REQUIRED (section 2.2.1). The security token issued.
488 pub access_token: String,
489 /// REQUIRED (section 2.2.1). An identifier for the representation of the issued token. Always
490 /// `urn:ietf:params:oauth:token-type:access_token` from this server.
491 pub issued_token_type: TokenTypeIdentifier,
492 /// REQUIRED (section 2.2.1), and REQUIRED even though the issued token "need not be an OAuth
493 /// access token": it states the method of using it. Always `Bearer` here (RFC 6750).
494 pub token_type: TokenType,
495 /// RECOMMENDED (section 2.2.1). This server always includes it.
496 #[serde(skip_serializing_if = "Option::is_none")]
497 pub expires_in: Option<u64>,
498 /// OPTIONAL if identical to the requested scope, REQUIRED otherwise (section 2.2.1). This
499 /// server always includes it when non-empty, which satisfies the stronger of the two: an
500 /// exchange narrows, so the issued scope frequently differs from the request.
501 #[serde(skip_serializing_if = "Option::is_none")]
502 pub scope: Option<String>,
503 /// OPTIONAL (section 2.2.1), and never issued by this server. See the module docs.
504 #[serde(skip_serializing_if = "Option::is_none")]
505 pub refresh_token: Option<String>,
506}
507
508impl fmt::Debug for TokenExchangeResponse {
509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510 f.debug_struct("TokenExchangeResponse")
511 .field("access_token", &"[redacted]")
512 .field("issued_token_type", &self.issued_token_type)
513 .field("token_type", &self.token_type)
514 .field("expires_in", &self.expires_in)
515 .field("scope", &self.scope)
516 .field(
517 "refresh_token",
518 &self.refresh_token.as_ref().map(|_| "[redacted]"),
519 )
520 .finish()
521 }
522}
523
524/// What an exchange produced: the wire response, plus the two things a host needs that are NOT
525/// wire response members.
526///
527/// `semantics` and `act` are separated from [`TokenExchangeResponse`] rather than living on it
528/// with `#[serde(skip)]`, because RFC 8693 section 2.2.1 defines the response members exhaustively
529/// and `act` is a TOKEN CLAIM (section 4.1), not a response parameter. A type that serialized to
530/// something the RFC does not define would be wrong in the one place this crate cannot afford to
531/// be approximate.
532#[derive(Debug, Clone, PartialEq, Eq)]
533pub struct ExchangedToken {
534 /// The RFC 8693 section 2.2.1 body, ready to serialize.
535 pub response: TokenExchangeResponse,
536 /// Whether this exchange was delegation or impersonation (section 1.1).
537 pub semantics: ExchangeSemantics,
538 /// The section 4.1 `act` claim, present exactly when `semantics` is
539 /// [`ExchangeSemantics::Delegation`]. See the module docs for why this crate hands it back
540 /// rather than putting it in its own opaque token.
541 pub act: Option<ActClaim>,
542}
543
544/// RFC 8693 token exchange, as an extension trait on [`AuthorizationServer`].
545///
546/// A trait rather than an inherent method because the grant is behind an off-by-default cargo
547/// feature, and a feature should add a name a reader can find rather than silently change the
548/// shape of a type they already know.
549pub trait TokenExchange {
550 /// Exchange `subject_token` for a new token, per RFC 8693 section 2.
551 ///
552 /// The caller MUST be a confidential client. RFC 8693 gives the AS the decision (section 2.1
553 /// leaves client authentication to the deployment), and this server decides the same way it
554 /// decides for client credentials, introspection and revocation: a public client identifier is
555 /// a string anyone may claim, so "authenticated as a public client" is a sentence true of
556 /// every caller on the internet. A grant whose entire job is to convert one principal's
557 /// authority into another's cannot rest on that.
558 ///
559 /// The client's registration must also include
560 /// [`crate::grant::GrantType::TokenExchange`], or the answer is `unauthorized_client`. Who may
561 /// exchange is a deployment decision, and this crate records deployment decisions about grants
562 /// in exactly one place.
563 fn exchange_token(
564 &self,
565 request: &TokenExchangeRequest<'_>,
566 ) -> impl std::future::Future<Output = Result<ExchangedToken, ErrorResponse>> + Send;
567}
568
569impl<S: Storage, C: Clock> TokenExchange for AuthorizationServer<S, C> {
570 async fn exchange_token(
571 &self,
572 request: &TokenExchangeRequest<'_>,
573 ) -> Result<ExchangedToken, ErrorResponse> {
574 let outcome = exchange(self, request).await;
575 // The same audit answer every other grant gets from
576 // `AuthorizationServer::token_with_resources`: a host that installed a sink hears about
577 // refusals, and a refused exchange is a more interesting line than most, because the thing
578 // being refused is usually an attempt to widen.
579 if let Err(error) = &outcome {
580 self.hooks().emit(|| Event::GrantRefused {
581 client_id: request.client_id.as_str(),
582 grant_type: GrantType::TokenExchange,
583 error: error.error,
584 });
585 }
586 outcome
587 }
588}
589
590/// The host sees the real error through its own `Storage` impl; the wire gets the opaque code.
591/// Same shape as `server.rs`'s own helper, which is private to that module.
592fn storage_error(e: StorageError) -> ErrorResponse {
593 let _ = e;
594 ErrorResponse::new(ErrorCode::ServerError)
595}
596
597/// The refusal for a sender-constrained subject token. One function for both mechanisms, so DPoP
598/// and mutual TLS cannot drift into two different answers for one rule.
599///
600/// Feature gated because both of its callers are, and a function with no caller is a warning the
601/// gate treats as an error.
602#[cfg(any(feature = "dpop", feature = "mtls"))]
603fn sender_constrained_refusal(mechanism: &str) -> ErrorResponse {
604 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(format!(
605 "subject_token is sender constrained by {mechanism} and cannot be exchanged for a token \
606 that is not, because the issued token would belong to a client that cannot prove \
607 possession of the binding key"
608 ))
609}
610
611/// How many actors an RFC 8693 section 4.1 `act` claim names, counting the outermost (current) one.
612///
613/// ITERATIVE rather than recursive, and that is the point of writing it out. The claim is a linked
614/// list this server READS BACK OUT OF ITS OWN STORE, and a store is something a host operates: a
615/// record deeper than the stack could take would be a crash on the token endpoint rather than a
616/// refusal, which is the failure mode `MAX_ACT_CHAIN_DEPTH` exists to prevent and not one to
617/// reintroduce in the counting. It also stops early, because the only question the caller asks is
618/// whether the bound has been reached.
619fn act_chain_depth(act: &ActClaim) -> usize {
620 let mut depth = 1;
621 let mut current = &act.act;
622 while let Some(next) = current {
623 depth += 1;
624 if depth >= MAX_ACT_CHAIN_DEPTH {
625 return depth;
626 }
627 current = &next.act;
628 }
629 depth
630}
631
632/// The exchange itself. Split out of the trait method so the audit emission above wraps every exit
633/// from it, exactly as `AuthorizationServer::emit_refusal` wraps the other four grants.
634async fn exchange<S: Storage, C: Clock>(
635 server: &AuthorizationServer<S, C>,
636 request: &TokenExchangeRequest<'_>,
637) -> Result<ExchangedToken, ErrorResponse> {
638 // 1. What is being asked for (section 2.1 `requested_token_type`). Checked FIRST and before
639 // any credential is looked at, because it costs nothing and because a client asking for a
640 // SAML assertion from a server that issues opaque bearer tokens has made a mistake that no
641 // amount of correct authentication will fix.
642 //
643 // Section 2.1 leaves the type to the server when the parameter is absent, and this server
644 // issues exactly one kind of token, so absent and `access_token` are the same request.
645 let issued_token_type = match request.requested_token_type {
646 None | Some(TokenTypeIdentifier::AccessToken) => TokenTypeIdentifier::AccessToken,
647 Some(_) => {
648 return Err(
649 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
650 "this server issues only urn:ietf:params:oauth:token-type:access_token",
651 ),
652 )
653 }
654 };
655
656 // 2. The client. See `TokenExchange::exchange_token` for why confidential only.
657 // RFC 8693 s2.1 authenticates the client the same way every other grant does, so it goes
658 // through the same value, and it carries EVERY credential shape the request could have
659 // presented rather than the shared secret alone. Section 2.1 points at RFC 6749 s2.3, which is
660 // a reference to whatever methods the server offers; `Bound::secret` discarded an RFC 7523
661 // assertion, so a confidential client registered for `private_key_jwt` or `client_secret_jwt`
662 // was answered `invalid_client` on a grant this server's RFC 8414 document advertises to it.
663 //
664 // No RFC 9449 binding, and that is now ENFORCED rather than assumed: this surface is not
665 // handed a proof, so a token issued here cannot be bound, and the HTTP router refuses a
666 // token-exchange request that presented one rather than quietly issuing an unbound token. See
667 // this module's "A SENDER-CONSTRAINED subject token is REFUSED" section, which makes the same
668 // argument about the subject token and would contradict itself if the ISSUED token were
669 // silently downgraded.
670 let bound = Bound {
671 cred: ClientCredential {
672 client_secret: request.client_secret,
673 #[cfg(feature = "client-assertion")]
674 client_assertion_type: request.client_assertion_type,
675 #[cfg(feature = "client-assertion")]
676 client_assertion: request.client_assertion,
677 // RFC 8705 is deliberately NOT threaded through here. A certificate authenticates AND
678 // binds (section 3), and this surface has nowhere to record the binding, so accepting
679 // one would issue an unbound token to a client that proved possession of a key: the
680 // silent downgrade this module refuses everywhere else. An mTLS-only client is
681 // refused `invalid_client` here, which is loud and is the honest answer until the
682 // exchange can carry a binding.
683 #[cfg(feature = "mtls")]
684 certificate: None,
685 },
686 #[cfg(feature = "dpop")]
687 jkt: None,
688 };
689 let client = server
690 .authenticate_client(request.client_id, &bound.cred)
691 .await?;
692 // BARE, and the reason goes to the audit channel. The third site of one shape: see the
693 // introspection and client-credentials twins in `crate::server`. A description here was
694 // reachable by a caller presenting NO credential at all (a public registration authenticates
695 // trivially), so it was the one answer on this endpoint meaning "this client id is registered,
696 // and it is public", while `authenticate_client` returns a bare `invalid_client` for both an
697 // unknown id and a wrong secret. The `unauthorized_client` below is NOT the same exposure: it
698 // is only reachable by a caller that has already proved a confidential credential.
699 if !client.auth.is_confidential() {
700 server.hooks().emit(|| Event::ClientAuthenticationFailed {
701 client_id: request.client_id.as_str(),
702 failure: crate::events::ClientAuthFailure::NotConfidential,
703 });
704 return Err(ErrorResponse::new(ErrorCode::InvalidClient));
705 }
706 if !client.allows_grant(GrantType::TokenExchange) {
707 return Err(ErrorResponse::new(ErrorCode::UnauthorizedClient)
708 .with_description("client registration does not include the token-exchange grant"));
709 }
710
711 // 3. The subject token (section 2.1). Only this server's own access tokens are accepted, and
712 // the type identifier has to SAY so: a caller that presents a refresh token and labels it
713 // an access token, or presents an access token and labels it a JWT, is asking this server
714 // to check the string a different way than it is going to. Refusing on the mismatch rather
715 // than on the lookup is what stops the type parameter becoming decorative.
716 if request.subject_token_type != TokenTypeIdentifier::AccessToken {
717 return Err(
718 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
719 "subject_token_type must be urn:ietf:params:oauth:token-type:access_token",
720 ),
721 );
722 }
723 let subject = server
724 .introspect(request.subject_token)
725 .await
726 .map_err(storage_error)?
727 // Unknown, expired and revoked are one answer: the caller holds a string this server will
728 // not exchange, and which of the three it is describes a token they may not hold.
729 .ok_or_else(|| {
730 ErrorResponse::new(ErrorCode::InvalidRequest)
731 .with_description("subject_token is not a live access token")
732 })?;
733
734 // 3b. The subject token's SENDER CONSTRAINING (RFC 9449 section 6, RFC 8705 section 3). A bound
735 // token may not be exchanged, because the token this server would issue is a BEARER token
736 // for a different client, and issuing it converts a token that survives theft into one that
737 // does not. The exchanging client proved possession of its own secret and of nothing else:
738 // it presented the subject token as a string, which is precisely what a thief also has. See
739 // the module docs for why this is a refusal rather than a propagated `cnf`.
740 //
741 // RFC 8693 section 2.2.2 gives `invalid_request` for a subject token that is unacceptable
742 // based on policy, which is what this is. The description names the mechanism because a
743 // legitimate client hitting this needs to know WHY, and it is not a secret: the client just
744 // presented the token that carries the binding.
745 //
746 // GUARDED by `ServerConfig::allow_sender_constrained_exchange`, which is `false` by
747 // default. The opt-in exists only because 0.9.0 and earlier performed this downgrade
748 // SILENTLY, so a deployment already built on it needs a migration window; a host that sets
749 // it has decided its delegation topology is trusted enough to hold the binding for it. What
750 // it gives up is stated on the field, and it is the whole of what DPoP or mutual TLS was
751 // bought for.
752 #[cfg(feature = "dpop")]
753 if subject.jkt.is_some() && !server.config().allow_sender_constrained_exchange {
754 return Err(sender_constrained_refusal("DPoP (RFC 9449)"));
755 }
756 #[cfg(feature = "mtls")]
757 if subject.x5t_s256.is_some() && !server.config().allow_sender_constrained_exchange {
758 return Err(sender_constrained_refusal("mutual TLS (RFC 8705)"));
759 }
760
761 // 4. The actor token (section 2.1), which is what makes this delegation rather than
762 // impersonation (section 1.1).
763 let (semantics, act) = match (request.actor_token, request.actor_token_type) {
764 (None, None) => (ExchangeSemantics::Impersonation, None),
765 // Section 2.1 makes actor_token_type REQUIRED when actor_token is present. The reverse is
766 // not a defined request at all, and reading it as impersonation would silently discard a
767 // parameter the client thought it was sending.
768 (None, Some(_)) => {
769 return Err(ErrorResponse::new(ErrorCode::InvalidRequest)
770 .with_description("actor_token_type is meaningless without actor_token"))
771 }
772 (Some(_), None) => {
773 return Err(ErrorResponse::new(ErrorCode::InvalidRequest)
774 .with_description("actor_token_type is required when actor_token is present"))
775 }
776 (Some(actor_token), Some(actor_token_type)) => {
777 if actor_token_type != TokenTypeIdentifier::AccessToken {
778 return Err(
779 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
780 "actor_token_type must be urn:ietf:params:oauth:token-type:access_token",
781 ),
782 );
783 }
784 // ONE refusal for every way an actor token can be unusable, built once so the two
785 // sites below cannot drift apart. See the comment on the ownership check for why the
786 // mismatch is not allowed its own wording.
787 let unusable = || {
788 ErrorResponse::new(ErrorCode::InvalidRequest)
789 .with_description("actor_token is not a live access token")
790 };
791 let actor = server
792 .introspect(actor_token)
793 .await
794 .map_err(storage_error)?
795 .ok_or_else(unusable)?;
796 // The acting party must be the party that just authenticated. Section 4.1 has the
797 // `act` claim "identify the acting party to whom authority has been delegated", and a
798 // server that will write any name there on production of a bearer string is not
799 // identifying anybody: it is transcribing. The client proved possession of its secret
800 // one step ago; the holder of a third party's access token proved possession of a
801 // string that leaks.
802 //
803 // THE REFUSAL IS THE SAME STRING AS THE ONE ABOVE, and that is the point rather than
804 // an economy. A description naming the ownership mismatch answers a question the
805 // caller was not entitled to ask: it says the string they presented IS a live access
806 // token, and that it belongs to somebody else. Any client holding this grant could
807 // then test arbitrary strings and learn which ones are live tokens of other clients,
808 // which is exactly the oracle `introspection_response` refuses to be for the same
809 // three cases ("unknown, expired, or somebody else's"), and which the subject token
810 // seventy lines above already collapses. A legitimate delegating client presents its
811 // OWN token here, so it never reads either string.
812 if actor.client_id != client.client_id {
813 return Err(unusable());
814 }
815 // THE PRIOR CHAIN, and how much of it this server will carry. Counted BEFORE anything
816 // is built, so a refusal costs one walk of a bounded list and no allocation.
817 if let Some(prior) = &subject.act {
818 if act_chain_depth(prior) >= MAX_ACT_CHAIN_DEPTH {
819 return Err(
820 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
821 "the subject token's act chain is already at this server's maximum \
822 delegation depth",
823 ),
824 );
825 }
826 }
827 let act = ActClaim {
828 // RFC 9068 section 2.2's answer for a grant with no resource owner is the client
829 // identifier, and the same answer is right here: an actor token minted by the
830 // client credentials grant names no user, and the acting party is then the client
831 // itself rather than nobody.
832 sub: actor
833 .subject
834 .clone()
835 .unwrap_or_else(|| actor.client_id.as_str().to_string()),
836 client_id: Some(actor.client_id.as_str().to_string()),
837 // Section 4.1's nesting expresses a chain of PRIOR actors, and this is where it
838 // comes from: whatever the SUBJECT token already recorded, moved one level in, so
839 // the new actor is the outermost and current one.
840 //
841 // This was `None` with a comment saying "this server does not yet carry `act`
842 // inside its own tokens", which 0.9.1 made false in the same file: the claim is
843 // persisted on `IssuedToken` and reported by introspection. Truncating on the
844 // strength of that stale comment turned A -> B -> C into `act={sub:C}`, where
845 // section 4.1 defines `{sub:C, act:{sub:B}}`, so a resource server auditing the
846 // chain was told the delegation started at B. Depth is bounded by
847 // `MAX_ACT_CHAIN_DEPTH`, checked above.
848 act: subject.act.clone(),
849 };
850 (ExchangeSemantics::Delegation, Some(act))
851 }
852 };
853
854 // 5. SCOPE, first ceiling. RFC 6749 section 6's narrowing rule, applied to the subject token's
855 // granted scope. This is the attack: the whole value of an exchange to an attacker is
856 // getting out more than went in.
857 let scope = match request.scope {
858 None => subject.scope.clone(),
859 Some(s) if s.is_subset(&subject.scope) => s.clone(),
860 Some(_) => {
861 return Err(
862 ErrorResponse::new(ErrorCode::InvalidScope).with_description(
863 "token exchange may narrow the subject token scope, never widen it",
864 ),
865 )
866 }
867 };
868 // SCOPE, second ceiling. The issued token belongs to the EXCHANGING client, so it cannot carry
869 // scope that client's own registration never permitted. See the module docs: without this, a
870 // read-only client that momentarily holds someone else's write token can mint itself one.
871 if !scope.is_subset(&client.allowed_scopes) {
872 return Err(ErrorResponse::new(ErrorCode::InvalidScope)
873 .with_description("scope exceeds the exchanging client registration"));
874 }
875 // RFC 9396 DETAILS, and the reason this refusal sits with the scope ceilings rather than near
876 // the propagation site: it is the SAME ceiling, and its absence was the whole defect.
877 //
878 // Scope gets two ceilings above. `authorization_details` got none: they were copied onto the
879 // issued token unchanged, justified by "it is exactly what the token the client just presented
880 // already carried". That is precisely the argument the second ceiling four lines up REJECTS for
881 // scope, and for the same reason, which is that the issued token belongs to the EXCHANGING
882 // client and not to the one the subject token was minted for.
883 //
884 // The asymmetry ran the wrong way. A RAR element is strictly more specific than the scope that
885 // accompanies it (RFC 9396 exists because a scope token cannot say "transfer 50 euros to IBAN
886 // X"), so the crate applied its weaker rule to its more dangerous grant. Concretely: a
887 // downstream service registered for `read`, holding a payments client's token because
888 // forwarding the caller's token is what this grant is FOR, could ask for `read`, satisfy both
889 // scope ceilings, and receive a token issued to ITSELF carrying the payment authorization, with
890 // a fresh lifetime that outlives the token it came from.
891 //
892 // There is nothing to narrow AGAINST: `Client` has `allowed_scopes` and no equivalent for
893 // detail types, so the per-client ceiling that would make this safe does not exist yet and
894 // adding it breaks a type hosts construct. So this refuses, and a host that has reasoned about
895 // its delegation topology can say so with `allow_authorization_details_exchange`.
896 //
897 // RFC 8693 section 2.2.2 names `invalid_request` for a subject token unacceptable on policy,
898 // which is what this is; `invalid_target` is for a requested RESOURCE the AS will not issue
899 // for, and the client requested no target here. The description names the mechanism because a
900 // legitimate client hitting this needs to know why, and it reveals nothing: the client is
901 // holding the token whose details these are.
902 //
903 // SCOPED TO A CROSS-CLIENT EXCHANGE, and that scoping is the argument rather than a
904 // convenience. Everything above turns on the issued token belonging to a DIFFERENT principal
905 // than the subject token. When the exchanging client IS the client the subject token was issued
906 // to, no boundary is crossed: it already holds those details, on a token it can already spend,
907 // and refusing would block a client from exchanging its own token for a narrower one, which is
908 // an ordinary and safe use of this grant. A blanket refusal here would have been over-broad,
909 // and the test that caught it was right to.
910 #[cfg(feature = "rar")]
911 if !subject.authorization_details.is_empty()
912 && subject.client_id != client.client_id
913 && !server.config().allow_authorization_details_exchange
914 {
915 return Err(
916 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
917 "subject_token carries authorization_details and cannot be exchanged for a token \
918 issued to a different client, because this server has no per-client registration of \
919 the detail types that client may hold",
920 ),
921 );
922 }
923
924 // 6. RESOURCE and AUDIENCE, the audience ceiling (RFC 8707 section 2, RFC 8693 section 2.1.1).
925 // `validate_resources` and `narrow_resources` are the SAME functions the authorization code
926 // and refresh grants use. That is deliberate and is the point: a second implementation of
927 // "may narrow, never widen" is a second thing to get wrong, and this one would be the one
928 // nobody reviews.
929 let mut targets = server.validate_resources(request.resource.iter().map(|r| r.as_str()))?;
930 // Section 2.1.1: `audience` names the same thing as `resource` in a form that need not be a
931 // URI. It is NOT run through `validate_resources`, which requires an absolute URI, because
932 // that would report a well-formed logical name as malformed; it goes to the same ceiling
933 // instead, where a name the subject token does not carry is `invalid_target` rather than
934 // `invalid_request`. A deployment whose audiences are not resource indicators therefore finds
935 // that no `audience` value is grantable here, which is the truth rather than a silent success.
936 // CAPPED, for the reason `validate_resources` is capped and with the same number: `audience`
937 // is repeatable, it is deduplicated against `targets` with an O(n) scan per element, and
938 // nothing bounded either side. Checked on the INPUT length rather than on `targets`, because a
939 // caller sending one value ten thousand times still pays a full scan per copy even though the
940 // result stays small.
941 if request.audience.len() > MAX_AUDIENCE_VALUES {
942 return Err(ErrorResponse::new(ErrorCode::InvalidTarget)
943 .with_description("too many audience values (RFC 8693 s2.1.1)"));
944 }
945 for audience in request.audience {
946 // SKIPPING THE SYNTAX CHECK IS NOT SKIPPING THE ALLOWLIST, and through 0.9.1 it was both.
947 // `ServerConfig::allowed_resources` is this server's RFC 8707 section 2 statement of what
948 // it is unwilling to issue for — the way an operator decommissions a resource server — and
949 // the `audience` spelling walked straight past it. An operator who removed R from that list
950 // went on handing out signed tokens whose `aud` names R, to any client holding a live token
951 // whose grant had recorded R, because `narrow_resources` below only asks whether the
952 // SUBJECT TOKEN carries the value and never whether the server still stands behind it.
953 server.target_is_permitted(audience)?;
954 if !targets.iter().any(|t| t == audience) {
955 targets.push(audience.clone());
956 }
957 }
958 // `narrow_and_permit`, not `narrow_resources`: the allowlist has to apply to what is ISSUED and
959 // not only to what was NAMED. The loop above covers an `audience` the request spelled out, but
960 // a request naming NEITHER `resource` nor `audience` inherits the subject token's whole
961 // recorded list untouched, and that list can name a resource server this deployment has since
962 // decommissioned. See `AuthorizationServer::narrow_and_permit`.
963 let resource = server.narrow_and_permit(&subject.resource, &targets)?;
964
965 // 7. Issue. Through the SAME `issue` every other grant goes through, so the exchanged token is
966 // persisted, introspectable, revocable and (with the `jwt` feature) signed exactly like any
967 // other token this server mints.
968 //
969 // `None` chain and `false` for refresh: RFC 8693 section 2.2.1 on why no refresh token, and
970 // a token with no chain has no family, which is right here because there is nothing to
971 // rotate and so nothing a reuse detection could revoke.
972 let issued = server
973 .issue(
974 &client,
975 &bound,
976 GrantType::TokenExchange,
977 // INHERITED from the subject token, not restamped. The exchanged token derives its
978 // authority from that grant, so a revocation reaching the grant must reach everything
979 // exchanged out of it; stamping `now` here would let an exchange launder a token past
980 // the revocation of the decision it descends from.
981 //
982 // THE INSTANT IS INHERITED AND THE IDENTITY IS NOT, and that is a gap rather than a
983 // decision. A `RevocationBarrier` for a withdrawn consent, and the cascade underneath
984 // it, both ask for the (client_id, subject) pair of the grant being ended; the token
985 // issued below carries the subject and the instant but belongs to the EXCHANGING
986 // client, so a cross-client exchange lands outside the reach of the withdrawal of the
987 // consent it descends from. `Storage::revoke_consent` states the same thing from the
988 // other side, with what it costs and what closing it needs. The lifetime ceiling at the
989 // end of this call is what bounds it: the descendant cannot outlive the token it was
990 // exchanged out of, so the overrun is one access token lifetime and not a fresh grant.
991 subject.grant_established_at,
992 subject.subject.clone(),
993 scope,
994 resource,
995 // RFC 9396: the exchanged token inherits the subject token's authorization details
996 // unchanged, and reaching this line at all means the ceiling above let it. Either the
997 // subject token carried no details, or the host set
998 // `allow_authorization_details_exchange` and accepted what that gives up.
999 //
1000 // The justification that used to sit here said propagation "is never a widening,
1001 // because it is exactly what the token the client just presented already carried". That
1002 // was false, and it was false in the same way for scope, where this file rejects the
1003 // identical argument two ceilings earlier: the issued token belongs to the EXCHANGING
1004 // client, so what the SUBJECT token was allowed to carry is not the question.
1005 crate::server::GrantedDetails::of_token(&subject),
1006 None,
1007 false,
1008 // RFC 8693 s1: a client is presenting a token, not a user presenting themselves.
1009 // Nobody authenticated during this request, so there is nothing to report, and
1010 // carrying the subject token's report forward would let an exchange launder a stale
1011 // authentication into a token that looks freshly stepped up.
1012 crate::server::GrantedAuthentication::default(),
1013 // THE DELEGATION, recorded on the token itself. `act` is `Some` only for the
1014 // delegation branch above; an impersonation exchange names no actor by definition.
1015 // This is what lets RFC 7662 introspection answer "A acting for B" rather than "B",
1016 // which for an OPAQUE token is the only channel that could.
1017 crate::server::GrantedActor {
1018 act: act.clone().map(Box::new),
1019 },
1020 // THE LIFETIME CEILING, and the reason it has to exist at all. The token issued here
1021 // is an ordinary access token, so it is itself an acceptable SUBJECT token, and
1022 // self-exchange is explicitly permitted a few ceilings above. Without a ceiling a
1023 // client could re-exchange just before each expiry and receive a fresh full
1024 // `access_token_ttl` every time, indefinitely, which renews the grant's lifetime
1025 // without limit. That is precisely what the "no refresh token is issued" rule in this
1026 // module's docs exists to prevent, defeated by the issued token's own type.
1027 //
1028 // `min(now + access_token_ttl, subject.expires_at)` is applied inside `issue`, so the
1029 // stored expiry, the RFC 9068 `exp` claim and the `expires_in` below all state the one
1030 // capped instant. An exchanged token can therefore be narrower in time as it is in
1031 // scope, details and audience, and never wider.
1032 Some(subject.expires_at),
1033 )
1034 .await?;
1035
1036 Ok(ExchangedToken {
1037 response: TokenExchangeResponse {
1038 access_token: issued.access_token,
1039 issued_token_type,
1040 token_type: issued.token_type,
1041 expires_in: Some(issued.expires_in),
1042 scope: issued.scope,
1043 // `issue` was told not to, and this asserts it rather than assuming it: section 2.2.1
1044 // makes the member optional, so a stray refresh token would serialize silently.
1045 refresh_token: None,
1046 },
1047 semantics,
1048 act,
1049 })
1050}
1051
1052#[cfg(test)]
1053#[path = "tests/token_exchange.rs"]
1054mod tests;