| Internet-Draft | OAuth Browser Session Handoff | April 2026 |
| Moros | Expires 18 October 2026 | [Page] |
This document specifies a usage profile that composes OAuth 2.0 Token Exchange [RFC8693] with a short-lived, single-use authorization code to establish an authenticated browser session at a Relying Party (RP) on behalf of a user authenticated at an Identity Provider (IdP) operating an independent OAuth 2.0 Security Token Service (STS). The server-to-server leg uses RFC 8693 to convey user identity and authorization context to the RP's STS, which issues an RP-scoped access token. A short-lived opaque code is then used to mediate delivery of session state into the user's browser without ever exposing the access token on the front channel.¶
The profile is designed to avoid the class of front-channel token leakage (browser history, HTTP Referer header, intermediary access logs) that motivated the deprecation of the OAuth 2.0 implicit grant in [RFC9700]. The profile also defines a minimum claims contract on the RP-issued access token to enable stateless authorization enforcement at the RP without synchronous callbacks to the IdP.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 18 October 2026.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
It is increasingly common for an organization (the "IdP-side party") authenticated at an Identity Provider to wish to present its users with a product surface operated by a second organization (the "RP-side party") on an RP-controlled domain, without requiring the user to re-authenticate at the RP. The OAuth 2.0 Token Exchange specification [RFC8693] provides a mechanism by which the IdP-side party's backend can exchange a subject token representing the user for an access token usable at the RP. However, RFC 8693 is, by its own Section 1, scoped to the server-to-server exchange and explicitly leaves usage context out of scope:¶
"The security tokens obtained may be used in a number of contexts, the specifics of which are also beyond the scope of this specification."¶
A naive extension of the exchange into the browser leg -- for example, placing the RP-issued access token in a redirect URL -- reintroduces the same class of front-channel token exposure that motivated deprecation of the OAuth 2.0 implicit grant. The current OAuth 2.0 Security Best Current Practice [RFC9700] makes this clear and recommends against transporting access tokens in the front channel.¶
This document specifies a profile that composes RFC 8693 with a short-lived, single-use authorization code to close the browser-leg gap. The profile is intentionally narrow: it does not modify RFC 8693, does not require changes to the IdP, and adds a single RP-side endpoint beyond the standard exchange endpoint. It is inspired by the authorization code flow of OAuth 2.0 [RFC6749] Section 4.1, adapted for a post-exchange browser handoff rather than an end-user authorization step.¶
A secondary contribution of this document is a minimum claims contract on the RP-issued access token, enabling the RP to enforce authorization statelessly without synchronous introspection or out-of-band synchronization with the IdP. This contract is designed to be compatible with existing JWT-based access tokens and does not require new registered claims beyond those already established in [RFC7519] and [RFC8693].¶
The profile defined in this document aims to satisfy the following:¶
The following are explicitly out of scope for this document:¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses the terms "access token", "authorization server", "client", "resource server", and "token endpoint" as defined in [RFC6749], and the terms "Claim" and "JWT Claims Set" as defined in [RFC7519]. In addition, the following terms are defined:¶
The profile has two trust legs: a server-to-server exchange (Leg A) and a browser-mediated handoff (Legs B and C). The RP access token is present only in server-side contexts from the moment of issuance through the establishment of the browser session.¶
+------------+ (A) RFC 8693 +------------+
| IdP | -----------------> | RP STS |
| Backend | <----------------- | |
+-----+------+ RP access token +-----+------+
| |
| (B) redirect with code | (B) cache token
| | under code
v v
+------------+ +------------+
| | (C) POST code | RP Session |
| Browser | ------------------> | Endpoint |
| | <------------------ | |
+------------+ Set-Cookie +------------+
Leg A is a standard RFC 8693 exchange: the IdP Backend presents a subject token obtained from the IdP to the RP STS. The RP STS validates the subject token against the IdP's published JWKS, applies policy, and returns an RP access token.¶
Leg B introduces the handoff code. The RP (either as part of the exchange response or via a separate endpoint; see Section 4.3) generates a handoff code and stores the RP access token in a server-side cache keyed by the code. The IdP Backend then responds to the user's browser with an HTTP redirect whose URL carries only the code.¶
Leg C is redemption. The browser, now on the RP-controlled domain, POSTs the code to the RP Session Endpoint. The RP atomically retrieves and invalidates the cached access token, establishes a server-side session, and sets a session cookie scoped to the RP domain.¶
The IdP MUST publish a JWKS [RFC7517] at a stable URL. The RP STS MUST retrieve and cache this JWKS and use it to validate subject token signatures. Symmetric keys (MUST NOT) be used for subject token signing in this profile.¶
The RP STS MUST publish its own JWKS at a stable URL for validation of RP-issued access tokens by downstream RP components.¶
The IdP Backend MUST authenticate to the RP STS
when initiating an exchange. [RFC7523] client
authentication using a private key
(private_key_jwt) is RECOMMENDED.
client_secret_basic MAY be used in
non-production environments but SHOULD NOT be
used in production deployments.¶
The IdP Backend sends an RFC 8693 token exchange request to the RP STS:¶
On success, the RP STS returns a standard RFC 8693 response per Section 2.2.1 of [RFC8693]:¶
The RP STS MUST, at minimum:¶
subject_token against the IdP's JWKS.¶
iss, aud, exp,
nbf, and iat claims of the subject token per
Section 4 of [RFC7519].¶
On any validation failure, the RP STS MUST respond per Section 2.2.2 of [RFC8693] and MUST NOT issue a token.¶
On issuing an RP access token intended for browser-based session establishment, the RP generates a handoff code and persists the mapping from code to RP access token (and associated metadata) in a short-lived server-side store.¶
The handoff code MUST have the following properties:¶
Having obtained a handoff code, the IdP Backend responds to the user's browser with an HTTP redirect:¶
A state parameter MAY also be included for
CSRF protection against the initiating IdP page, per standard
OAuth 2.0 redirect hygiene.¶
Two implementation shapes are conformant; the interface contract is relevant to the IdP-side implementer:¶
Combined response: The RFC 8693 response is extended with a handoff code alongside the access token. This minimizes round trips but requires the IdP Backend to handle a non-standard response extension.¶
Separate issuance endpoint: After the RFC 8693 exchange, the IdP Backend calls a second RP endpoint, passing the RP access token and receiving a handoff code. This is cleaner separation between the standard (RFC 8693) and profile-specific concerns and is the RECOMMENDED shape.¶
The browser, having arrived at the RP handoff page, client-side code on that page MUST immediately POST the code to the RP Session Endpoint:¶
The request is same-origin. The RP Session Endpoint
MUST verify the Origin header.¶
On receiving a redemption request, the RP Session Endpoint MUST:¶
exp.¶
The cookie MUST carry the HttpOnly and
Secure attributes. SameSite=Lax is the
RECOMMENDED default; SameSite=Strict
SHOULD be used where the post-login flow permits.¶
On any redemption failure -- unknown code, already-consumed code, expired code, client binding mismatch, expired cached token -- the RP Session Endpoint MUST return a generic error response. Distinguishing failure reasons in the response body MUST NOT occur, to avoid enabling code-probing oracles. Distinct failure reasons SHOULD be recorded in server logs with correlation identifiers for operational visibility.¶
This section specifies the minimum set of claims the RP-issued access token MUST carry to support stateless authorization enforcement at the RP.¶
In addition to the standard JWT claims iss,
sub, aud, exp, iat, and
jti defined in [RFC7519], the following
claims MUST appear:¶
tenant_id (string):perms (array of strings):scope (string):
Permissions SHOULD be expressed as colon-delimited
resource:action strings, for example
reports:read, records:write,
admin:users:read.¶
This shape is preferred over named roles because it allows the RP
to enforce directly without maintaining a role-to-permission
mapping. Where the IdP's authorization model is role-based, a
snapshot of derived permissions SHOULD be
materialized into the perms claim at token mint time;
a roles claim MAY additionally be
included for informational purposes, but the RP
MUST enforce against perms.¶
The following claims MAY appear:¶
The access token exp SHOULD be between 15
and 60 minutes. Shorter values improve the timeliness of
authorization changes at the cost of exchange volume. The RP
session cookie lifetime MAY exceed the access
token lifetime if the RP implements silent re-exchange; such a
mechanism is out of scope for this document.¶
The primary motivation for this profile is to prevent the RP access token from appearing in any front-channel context. This profile ensures the access token is present only in: the RFC 8693 response over TLS; the RP-side cache keyed by the handoff code; and the RP-side session record. The token does not appear in any URL, browser storage, HTTP Referer header, or intermediary log.¶
The handoff code appears in a URL and is therefore exposed to browser history, access logs at the RP domain, and (potentially) Referer headers if the handoff page links outward. Because the code is single-use and short-lived, exposure after consumption is harmless. Exposure during the redemption window is the only attack surface.¶
Mitigations:¶
The RP STS MUST validate that the presented
subject token was intended for use in an exchange to the RP.
An IdP-issued token minted for an unrelated purpose
MUST NOT be accepted. Acceptance
SHOULD be gated on an explicit aud
value or dedicated scope agreed between IdP and RP at
onboarding.¶
The RP handoff page MUST:¶
Referrer-Policy: no-referrer.¶
Cache-Control: no-store.¶
All HTTP interactions in this profile MUST use TLS 1.2 or higher. Plaintext HTTP MUST be rejected at all endpoints.¶
RP STS, IdP Backend, and IdP clocks SHOULD be
synchronized. A small skew tolerance of 30 to 60 seconds
SHOULD be applied when validating
exp, nbf, and iat. The handoff
code TTL SHOULD NOT be inflated to absorb
clock skew.¶
The RP STS SHOULD rate-limit exchange requests per authenticated client. The RP Session Endpoint MUST rate-limit redemption attempts per source IP to defend against code-probing, notwithstanding the impracticality of guessing high-entropy codes.¶
Because the RP enforces authorization from the access token alone, permission changes made at the IdP between token-mint events are not visible to the RP until the next exchange. Deployments that require near-real-time revocation SHOULD adopt one of: OAuth 2.0 Token Introspection [RFC7662]; a revocation signal channel (for example, webhook or event stream); or sufficiently short token lifetimes combined with silent re-exchange. Selection among these is a deployment concern and is out of scope for this profile.¶
The RP access token and the subject token may both carry
privacy-sensitive information about the end user, including
identifiers, tenancy, and permissions. Both
MUST be transmitted only over TLS.
Deployments SHOULD practice data minimization:
the perms claim should contain only permissions
relevant to the RP's scope, not the user's full rights at the
IdP.¶
The handoff code itself carries no personal information and is not a privacy concern beyond the exposure of its existence in URL contexts.¶
This document has no IANA actions. It does not define new grant types, token type identifiers, JWT claims, or OAuth parameters. All identifiers used are drawn from existing registrations established by [RFC6749], [RFC7519], and [RFC8693].¶
This profile draws directly on the design patterns of [RFC8693] and Section 4.1 of [RFC6749]. Reviewers are thanked for their feedback, which motivated the explicit separation of the server-to-server exchange from the browser handoff.¶