Skip to content

Add OIDC OAuth callback and UserInfo#5641

Open
Pantus Oleh (zibet27) wants to merge 1 commit into
zibet27/oidc-token-validation-bearerfrom
zibet27/oidc-oauth-callback-userinfo
Open

Add OIDC OAuth callback and UserInfo#5641
Pantus Oleh (zibet27) wants to merge 1 commit into
zibet27/oidc-token-validation-bearerfrom
zibet27/oidc-oauth-callback-userinfo

Conversation

@zibet27

Copy link
Copy Markdown
Collaborator

Subsystem
Server Auth OIDC

Motivation
KTOR-5001 Add OpenID Connect (OIDC) support on client and server

Solution
Adds the OIDC browser login/callback flow and UserInfo support on top of provider lifecycle and token validation.

This introduces

  • OAuth/OIDC login and callback routes
  • authorisation transaction state and nonce storage
  • ID-token validation
  • UserInfo fetching, and signed UserInfo validation

PKCE, persistent sessions/logout, token introspection, resource indicators, and protected resource metadata stay in later PRs.

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • main
  • release/*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5c835331-ffac-4247-80a0-d31452572222

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR extends Ktor's OpenID Connect plugin with OAuth 2.0 login flow support, enabling authentication via OAuth callbacks alongside existing Bearer token validation. New public configuration APIs define OAuth credentials and callbacks; internal transaction storage manages authorization state. The implementation includes OAuth flow creation, login/callback routing, enhanced token verification with optional UserInfo fetching, and comprehensive test coverage.

Changes

OIDC OAuth 2.0 Login Flow Implementation

Layer / File(s) Summary
Public OAuth Configuration API
ktor-server/ktor-server-plugins/ktor-server-auth-openid/api/ktor-server-auth-openid.api, jvm/src/io/ktor/server/auth/openid/OidcConfig.kt
New public OidcOAuthConfig<P> class for OAuth client credentials, scopes, audience, and callback configuration; extended OidcProviderConfig with oauth { } method; validation enforcing openid scope requirement and mutual clientId/clientSecret initialization.
Authorization Transaction Store
jvm/src/io/ktor/server/auth/openid/OidcAuthorizationTransactionStore.kt
Thread-safe in-memory store with TTL-based expiration and periodic pruning for managing per-session authorization state (nonce/state pairs); cookie-based session tracking and authorization transaction lifecycle (create, find, consume).
OAuth Flow Creation and Integration
jvm/src/io/ktor/server/auth/openid/OidcBearer.kt
OAuth2Flow construction from provider OAuth config with OIDC metadata and client settings; authorize URL interception to inject nonce from authorization transaction; state verification with issuer validation and authorization transaction existence check.
OAuth Login & Callback Routing
jvm/src/io/ktor/server/auth/openid/OidcOAuth.kt, jvm/src/io/ktor/server/auth/openid/OidcConfig.kt
/login endpoint generating per-request nonce/state and authorization URL; /callback routing validating issuer, consuming authorization transaction, exchanging code for tokens, transforming principal, and dispatching onSuccess/onFailure handlers.
Token Verification and OAuth Principal Building
jvm/src/io/ktor/server/auth/openid/OidcTokens.kt
New buildOAuthPrincipal differentiating ID-token vs access-token-only flows; buildVerifiedPrincipal for ID-token verification with nonce and at_hash validation; token-type-aware JWT verification selecting allowed algorithms per token kind; optional UserInfo HTTP fetching with signed JWT validation.
Provider Initialization and OAuth Wiring
jvm/src/io/ktor/server/auth/openid/OidcProvider.kt, jvm/src/io/ktor/server/auth/openid/Oidc.kt
Extended OidcProvider with internal oauthFlow and authorizationTransactionStore fields; OAuth flow initialization during scheme creation; plugin calls configureOAuthRoute(provider) during provider commit to install OAuth routes.
Plugin Configuration and Documentation
jvm/src/io/ktor/server/auth/openid/Oidc.kt, jvm/src/io/ktor/server/auth/openid/OidcConfig.kt
Updated KDoc examples and descriptions across plugin to reflect OAuth configuration alongside Bearer authentication; environment-based OAuth defaults when clientId is present; clarification of JWT settings, provider naming, and algorithm selection behavior.
OAuth Test Utilities
jvm/test/io/ktor/server/auth/openid/utils/OidcTestOAuthFlow.kt, jvm/test/io/ktor/server/auth/openid/utils/OidcTestProviders.kt, jvm/test/io/ktor/server/auth/openid/utils/OidcTestUtils.kt
Helper functions for preparing OIDC login flows, extracting authorization session cookies, and mocked external OpenID provider with /token endpoint; utilities for authorization code validation and ID token response construction.
OAuth Callback and Security Tests
jvm/test/io/ktor/server/auth/openid/OidcOAuthCallbackTest.kt, jvm/test/io/ktor/server/auth/openid/OidcOAuthCallbackSecurityTest.kt, jvm/test/io/ktor/server/auth/openid/OidcOAuthAccessTokenFallbackTest.kt
Functional tests for successful OAuth callbacks without sessions, error handling, lowercase bearer token acceptance, redirect URI construction, OAuth + bearer interoperability; security tests for token type validation, nonce verification, authorization session cookie matching, issuer validation, and access token fallback.
Configuration, Store, and Verification Tests
jvm/test/io/ktor/server/auth/openid/OidcAuthorizationTransactionStoreTest.kt, jvm/test/io/ktor/server/auth/openid/OidcConfigValidationTest.kt, jvm/test/io/ktor/server/auth/openid/OidcEnvironmentConfigTest.kt, jvm/test/io/ktor/server/auth/openid/OidcJwtKeyAndAlgorithmTest.kt, jvm/test/io/ktor/server/auth/openid/OidcUserInfoTest.kt
Authorization transaction store semantics (replacement, TTL expiration, pruning, concurrency); OAuth configuration validation including route URI query parameter rejection; environment configuration extension patterns and partial credential rejection; ID token algorithm selection from discovery metadata; UserInfo JWT verification.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

👍 ship!

Suggested reviewers

  • e5l
  • nomisRev
  • bjhham
  • osipxd
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add OIDC OAuth callback and UserInfo' directly describes the main changes—introducing OAuth callback flows and UserInfo support for OIDC—and is aligned with the PR's core objectives.
Description check ✅ Passed The description follows the template with Subsystem, Motivation (with ticket reference KTOR-5001), and Solution sections. All required sections are present and adequately filled with clear information about what is being implemented.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch zibet27/oidc-oauth-callback-userinfo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zibet27

Copy link
Copy Markdown
Collaborator Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcAuthorizationTransactionStore.kt`:
- Around line 29-41: entries is currently keyed only by authorizationSessionId
causing parallel logins to clobber each other; modify the store to key by both
session and state (e.g., use a composite key string
"${authorizationSessionId}:$state" or change entries to Map<String, Map<String,
Entry>>) and update all related methods (put, lookup/get, remove/pruneExpired)
to use the new keying strategy so each OidcAuthorizationTransaction is preserved
per (authorizationSessionId, state) pair; ensure Entry,
put(authorizationSessionId, state, transaction), pruneExpired(), and any
retrieval/remove logic refer to the composite/nested structure consistently.

In
`@ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcConfig.kt`:
- Around line 451-457: The validate() function currently only checks that
clientId and clientSecret are initialized, allowing empty strings; update
validate() (in OidcConfig) to first require(::clientId.isInitialized) and
::clientSecret.isInitialized as it does, then additionally require that
clientId.isNotBlank() and clientSecret.isNotBlank(), e.g. add
require(clientId.isNotBlank()) { "clientId must be configured and non-blank" }
and require(clientSecret.isNotBlank()) { "clientSecret must be configured and
non-blank" } so blank values fail early during configuration.

In
`@ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcOAuth.kt`:
- Around line 58-74: The try block currently wraps the entire authentication
flow including oauthConfig.onSuccess, causing exceptions from the success
handler to be treated as auth failures; move the call to
oauthConfig.onSuccess(this, typedPrincipal) out of the try/catch so that only
the provider/validation/creation steps
(provider.validateAuthorizationResponseIssuer,
provider.consumeAuthorizationTransaction, provider.buildOAuthPrincipal,
provider.transformPrincipal) are guarded; keep the catch handlers to call
oauthConfig.onFailure only for exceptions from those guarded steps, and allow
exceptions thrown by oauthConfig.onSuccess to propagate (or handle them
separately) to avoid converting real handler errors into 401s or causing a
second response write.

In
`@ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcTokens.kt`:
- Around line 173-180: The call to fetchUserInfo currently passes
idTokenAudience as the audience used to validate a signed UserInfo JWT, which is
wrong for UserInfo responses; update the call sites (including the other
occurrences around the 193–216 block) to pass oauthConfig.clientId as the
expected audience instead of idTokenAudience (i.e., replace expectedAudience =
idTokenAudience with expectedAudience = oauthConfig.clientId), keeping the other
parameters (userInfoEndpoint, accessToken, idTokenSubject, metadata,
jwkProvider) unchanged so UserInfo JWT audience is validated against the OAuth
client ID.
- Around line 83-88: The access-token-only branch currently skips validating the
token_type; update the branch that returns
requireNotNull(verifyAccessToken(response.accessToken)) to first assert
response.tokenType (or response.token_type) equals "Bearer" (same check used in
the id_token path) and fail with a clear message if not, and only then call
verifyAccessToken; reference config.accessTokenConfig,
response.tokenType/response.token_type, verifyAccessToken, and
OidcPrincipal.AccessToken so the check is placed where the access-token-only
flow returns the AccessToken principal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c3410495-35e0-450b-a66c-e7968230bd2e

📥 Commits

Reviewing files that changed from the base of the PR and between cdfd0df and fd6b458.

📒 Files selected for processing (19)
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/api/ktor-server-auth-openid.api
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/Oidc.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcAuthorizationTransactionStore.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcBearer.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcConfig.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcOAuth.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcProvider.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcTokens.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcAuthorizationTransactionStoreTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcConfigValidationTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcEnvironmentConfigTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcJwtKeyAndAlgorithmTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcOAuthAccessTokenFallbackTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcOAuthCallbackSecurityTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcOAuthCallbackTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/OidcUserInfoTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/utils/OidcTestOAuthFlow.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/utils/OidcTestProviders.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/test/io/ktor/server/auth/openid/utils/OidcTestUtils.kt

Comment on lines +451 to +457
internal fun validate(accessTokenAllowed: Boolean) {
require(::clientId.isInitialized) {
"clientId must be configured"
}
require(::clientSecret.isInitialized) {
"clientSecret must be configured"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject blank OAuth client credentials.

validate() only checks initialization here, so clientId = "" or clientSecret = "" still passes and then fails later during the redirect/token exchange. Please require both values to be non-blank at config time.

Suggested fix
     internal fun validate(accessTokenAllowed: Boolean) {
-        require(::clientId.isInitialized) {
-            "clientId must be configured"
+        require(::clientId.isInitialized && clientId.isNotBlank()) {
+            "clientId must be configured and non-blank for provider $providerName"
         }
-        require(::clientSecret.isInitialized) {
-            "clientSecret must be configured"
+        require(::clientSecret.isInitialized && clientSecret.isNotBlank()) {
+            "clientSecret must be configured and non-blank for provider $providerName"
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcConfig.kt`
around lines 451 - 457, The validate() function currently only checks that
clientId and clientSecret are initialized, allowing empty strings; update
validate() (in OidcConfig) to first require(::clientId.isInitialized) and
::clientSecret.isInitialized as it does, then additionally require that
clientId.isNotBlank() and clientSecret.isNotBlank(), e.g. add
require(clientId.isNotBlank()) { "clientId must be configured and non-blank" }
and require(clientSecret.isNotBlank()) { "clientSecret must be configured and
non-blank" } so blank values fail early during configuration.

Comment on lines +173 to +180
fetchUserInfo(
endpoint = userInfoEndpoint,
accessToken = accessToken,
expectedSubject = idTokenSubject,
expectedAudience = expectedAudience,
metadata = metadata,
jwkProvider = jwkProvider,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use clientId for signed UserInfo audience validation.

idTokenAudience is documented as ID-token-specific, but the same value is passed into fetchUserInfo() and then used to verify the signed UserInfo JWT audience. With fetchUserInfo = true, overriding idTokenAudience can reject an otherwise valid UserInfo response addressed to the OAuth client. Validate UserInfo against oauthConfig.clientId instead.

Also applies to: 193-216

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcTokens.kt`
around lines 173 - 180, The call to fetchUserInfo currently passes
idTokenAudience as the audience used to validate a signed UserInfo JWT, which is
wrong for UserInfo responses; update the call sites (including the other
occurrences around the 193–216 block) to pass oauthConfig.clientId as the
expected audience instead of idTokenAudience (i.e., replace expectedAudience =
idTokenAudience with expectedAudience = oauthConfig.clientId), keeping the other
parameters (userInfoEndpoint, accessToken, idTokenSubject, metadata,
jwkProvider) unchanged so UserInfo JWT audience is validated against the OAuth
client ID.

@e5l Leonid Stashevsky (e5l) removed their request for review June 1, 2026 08:58
@zibet27 Pantus Oleh (zibet27) force-pushed the zibet27/oidc-token-validation-bearer branch 2 times, most recently from 3763385 to 6e1c5f4 Compare June 11, 2026 08:39
@zibet27 Pantus Oleh (zibet27) force-pushed the zibet27/oidc-oauth-callback-userinfo branch 2 times, most recently from efcebf4 to 54e5f6f Compare June 11, 2026 12:14
Co-authored-by: Cursor <cursoragent@cursor.com>
@zibet27 Pantus Oleh (zibet27) force-pushed the zibet27/oidc-oauth-callback-userinfo branch from 54e5f6f to dc16069 Compare June 11, 2026 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant