Prerequisites
Impacted areas (select all that apply)
Problem statement
Today the OAuth2 flow erases OpenID Connect extras by constraining to StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>. The mapper only sees access/refresh tokens, so consumers must issue an extra UserInfo HTTP call to get claims that are already present in the id_token (e.g., sub, email, email_verified, hd, nonce, iss, aud). This adds latency, another failure mode, and blocks the usage of providers that don't provide a userinfo endpoint.
Proposed solution (high-level)
Allow OIDC token responses (with id_token) to reach with_account_mapper, enabling consumers to validate and extract claims without an extra UserInfo request, before minting the first-party JWT.
API design details
* Widen the mapper input to a token response that retains `id_token` or allows generalization to change the expected response type.
* Surface the raw `id_token` and possibly decoded claims to the mapper; handle JWT verification in the library.
* Consider an additive API (`with_oidc_account_mapper`) or a type alias to keep compatibility; avoid breaking existing mappers.
Primary use-cases and examples
As a consumer of Google/Microsoft OIDC, I want to validate id_token nonce/iss/aud and use sub/email_verified/hd to build my Account without calling the UserInfo endpoint.
// Pseudo code
Gate::oauth2::<Role, Group>()
.add_scope("openid")
.with_account_mapper(|token| {
let id_token = token.id_token().ok_or(...)?
// validate nonce/aud/iss, decode claims, map to Account
let claims = id_token
.claims(&id_token_verifier, &nonce)
.context("Failed to verify ID token")?;
let provider_subject = claims.subject().as_str().to_string();
let name = claims
.name()
.and_then(|n| n.get(None).map(|n| n.to_string()));
let email = claims
.email()
.ok_or_else(|| anyhow!("Email is required"))?
.to_string();
// Add user info elsewhere.
let user_id = format!("PROVIDER-UUID:{provider_subject}");
Ok(Account::<Role, Group>::new(&user_id, &[Role::User], &[]))
})
.with_jwt_codec("my-app", codec, 86400);
Alternatives considered
Honestly, I'm not a rust wizard, yet.
Prior art and references
No response
Breaking changes
Possibly (soft break, deprecations)
Breaking changes and migration strategy
No response
Feature flag
Unsure
Configuration surface
No response
Security and privacy considerations
- Encourages/enables validation on
id_token claims.
- Reduces attack surface, since we don't need to make another request to userinfo and the info that we have is verified via a crypto signature.
- On the flip side, if a service must handle providers like GitHub and other proper OIDC providers (Google, Microsoft, Tailscale OIDC), simultaneously, then we may have increased the complexity, since this account handler would conditionally use the
id_token claims for normal providers, but would make a userinfo request for GitHub.
Performance implications
No response
Observability (logging/metrics/tracing)
No response
Documentation and examples
No response
Acceptance criteria
Contribution readiness
Contact (optional)
@linux4life798
Prerequisites
Impacted areas (select all that apply)
Problem statement
Today the OAuth2 flow erases OpenID Connect extras by constraining to
StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>. The mapper only sees access/refresh tokens, so consumers must issue an extra UserInfo HTTP call to get claims that are already present in theid_token(e.g., sub, email, email_verified, hd, nonce, iss, aud). This adds latency, another failure mode, and blocks the usage of providers that don't provide a userinfo endpoint.Proposed solution (high-level)
Allow OIDC token responses (with
id_token) to reachwith_account_mapper, enabling consumers to validate and extract claims without an extra UserInfo request, before minting the first-party JWT.API design details
Primary use-cases and examples
As a consumer of Google/Microsoft OIDC, I want to validate id_token nonce/iss/aud and use sub/email_verified/hd to build my Account without calling the UserInfo endpoint. // Pseudo code Gate::oauth2::<Role, Group>() .add_scope("openid") .with_account_mapper(|token| { let id_token = token.id_token().ok_or(...)? // validate nonce/aud/iss, decode claims, map to Account let claims = id_token .claims(&id_token_verifier, &nonce) .context("Failed to verify ID token")?; let provider_subject = claims.subject().as_str().to_string(); let name = claims .name() .and_then(|n| n.get(None).map(|n| n.to_string())); let email = claims .email() .ok_or_else(|| anyhow!("Email is required"))? .to_string(); // Add user info elsewhere. let user_id = format!("PROVIDER-UUID:{provider_subject}"); Ok(Account::<Role, Group>::new(&user_id, &[Role::User], &[])) }) .with_jwt_codec("my-app", codec, 86400);Alternatives considered
Honestly, I'm not a rust wizard, yet.
Prior art and references
No response
Breaking changes
Possibly (soft break, deprecations)
Breaking changes and migration strategy
No response
Feature flag
Unsure
Configuration surface
No response
Security and privacy considerations
id_tokenclaims.id_tokenclaims for normal providers, but would make a userinfo request for GitHub.Performance implications
No response
Observability (logging/metrics/tracing)
No response
Documentation and examples
No response
Acceptance criteria
id_tokenJWT to user codeContribution readiness
Contact (optional)
@linux4life798