Skip to content

Add typed OAuth with session support#5567

Draft
Pantus Oleh (zibet27) wants to merge 6 commits into
typed-auth-dslfrom
typed-oauth-with-session
Draft

Add typed OAuth with session support#5567
Pantus Oleh (zibet27) wants to merge 6 commits into
typed-auth-dslfrom
typed-oauth-with-session

Conversation

@zibet27

Copy link
Copy Markdown
Collaborator

Subsystem
Server Auth

Motivation
https://github.com/ktorio/ktor-klip/blob/zibet27/auth-3.5/proposals/0006-auth-3.5.md

Why oauthWithSession

The existing OAuth provider is useful for completing the OAuth callback and obtaining an OAuthAccessTokenResponse, but it is easy to read it as a provider that can protect application routes directly.

That is misleading for typical web login flows: OAuth authenticates only the callback request. It does not persist the login state, create an application principal, or protect later requests. Applications still need to map the token response to their own session/principal and then use session authentication for protected routes.

oauthWithSession makes this pattern explicit:

  • OAuth handles the redirect/callback flow.
  • The callback maps the OAuth token response to an application session.
  • Protected routes authenticate via that session and expose a typed principal.

This keeps the OAuth provider focused on OAuth, while giving users a typed, end-to-end login flow that matches how OAuth is commonly used in server-side applications.

This is still a question if we want to just provide typed DSL for oauth or oauthWithSession, as shown in this PR

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

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: bbf597f5-8870-47a9-bb43-2c6d7ffa5e97

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

Typed session auth now uses SessionContext and SessionTransport. OAuth2 server settings add state verification and extra token parameters, request handling uses those hooks, and a typed OAuth flow DSL adds route/application installers and tests for callback and session flows.

Changes

OAuth auth and typesafe session refactor

Layer / File(s) Summary
Session context and transport contract
ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.api, ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.klib.api, ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/Scopes.kt, .../SessionTransport.kt, .../SessionAuthScheme.kt, .../TypedSessionAuthConfig.kt, .../BasicTypedProvider.kt, .../SessionsConfigTypesafeExtensions.kt
SessionContext replaces SessionAuthenticatedContext, SessionTransport becomes the session transport discriminator, and typed session config/scheme helpers move to the two-parameter SessionAuthScheme shape.
OAuth2 state and token handling
ktor-server-ktor-server-plugins-ktor-server-auth/api/ktor-server-auth.api, ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.klib.api, ktor-server/.../OAuthCommon.kt, .../OAuth2.kt, .../OAuthProcedure.kt, .../AuthenticationInterceptors.kt, common/test/io/ktor/tests/auth/OAuth2Test.kt
OAuth2ServerSettings adds state verification and extra token parameters, access-token requests use those hooks, callback bodies are cached, and OAuth2 tests cover the new request behavior.
Typed OAuth flow DSL and routing
ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.api, ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.klib.api, ktor-server/.../typesafe/OAuthFlow.kt, common/test/io/ktor/tests/auth/typesafe/OAuthFlowTest.kt
Typed OAuth flow configs and installers add non-session and session-backed OAuth routing, with tests for callback, session, login, and validation paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.23% 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 is concise and matches the main change: adding typed OAuth with session support.
Description check ✅ Passed It includes Subsystem and Motivation, and the solution is described in the body, though there's no explicit Solution heading.
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 typed-oauth-with-session

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.

@zibet27 Pantus Oleh (zibet27) marked this pull request as draft April 30, 2026 09:19
@osipxd

Copy link
Copy Markdown
Member

Could you pull the latest main? Failing tests seem to be fixed in the main branch.

* @return a compound scheme for [oauthCallback] and [authenticateWith].
*/
@ExperimentalKtorApi
public inline fun <reified S : Any, reified P : Any> oauthWithSession(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the seprate method looks adhoc for me, could we make it more unified and have session support inside the regular one?

Simon Vergauwen (@nomisRev), what do you think?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We can
I didn't include the typed oauth scheme in the #5565
oauthWithSession is named like this to make clear that session integration is provided by default

cookie(scheme = it)
}
sessionCreator = { token ->
val oauth2 = token as OAuthAccessTokenResponse.OAuth2

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.

Why is this cast needed here? We should have typed access the accessToken and the other information from the successfully logged in principal, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, but the type of token is OAuthAccessTokenResponse, not oauth2

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Actually, this is a bug
I've reduced the token type to OAuthAccessTokenResponse.OAuth2, but the session creator gets raw OAuthAccessTokenResponse (I forgot it or lost it at some point)
Good catch!

@nomisRev

Simon Vergauwen (nomisRev) commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Leonid Stashevsky (@e5l) I am wondering if we might need to rethink some of these APIs a little bit because it still won't be straightforward for a developer to allow secure access from a server, there are a lot of different moving parts here. There was a secondary goal besides introducing "typed authentication" which was to simplify setting up secure OAuth with session.

Even though the PR is small it's kind-of tricky to wrap my head around but user needs to separately setup:

  • login route
  • callback route (not huge fan of the API/signature in this PR)
  • oauthflow.withSessions { }
    • Verify sessions with JWK

And/or is the goal to tackle this in ktorio/ktor-klip#7? In that case this can still be considered low-level, but we might want to deviate more from the current (old) design. Perhaps we should consider skipping this all together, and jumping straight to KLIP 7 to provide a high-level typed DSL? 🤔

WDYT Pantus Oleh (@zibet27)?

@zibet27

Pantus Oleh (zibet27) commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author
  • callback route (not huge fan of the API/signature in this PR)

Also, not 100% sure about this one
The idea here is to show that the OAuth flow is not a "proper" authentication scheme and should be used only for such callbacks

And/or is the goal to tackle this in ktorio/ktor-klip#7? In that case, this can still be considered low-level

Yeah, this PR is a low-level API for easier migration from the existing API and a base for OIDC

but we might want to deviate more from the current (old) design

Personally, I am open to more changes. This is a new API anyway
Just not sure which direction you want to move

Perhaps we should consider skipping this all together, and jumping straight to KLIP 7 to provide a high-level typed DSL?

So you mean, not to add Typesafe OAuth DSL and only keep OIDC?
We could omit this atm and see if people would really need it, though it feels too inconsistent for me

* @return the route that contains the OAuth callback.
*/
@ExperimentalKtorApi
public fun Route.oauthCallback(

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.

Maybe this could be slightly nicer signature?

context(route: Route)
fun OAuth2Flow.oauthCallback(path: String, onSuccess: ...): Route

@nomisRev

Simon Vergauwen (nomisRev) commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Okay, I gave this some more thought and it might make sense to have this intermediate APIs. Perhaps for people that have a more complex setup with the current low-level APIs that will not be able to easily migrate to the higher-level OIDC support. It might indeed feel inconsistent, and to be honest I'm personally open to removing or deprecating the "old" code although that'd be a huge breaking change. Maybe deprecate in Ktor 4 and in 10 years remove in Ktor 5.0 😅

I think what bothers me a little bit here is that the routes are still "implicitly" linked, and without them it doesn't work at all. So you have the OAuth2 configured but it's incomplete. So maybe we just need to:

  • Combine urlProvider with the callback handler somehow?
  • Introduce login path to the configuration rather than using the "empty handler" approach that we now have.

This unifies the OAuth setup a little bit better in my opinion but I think it breaks the current oauthCallback mechanism with -and without sessions but I'm not sure we're missing out of a lot there or maybe I overlooked something?

# Conflicts:
#	ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.api
#	ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.klib.api
@zibet27

Copy link
Copy Markdown
Collaborator Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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: 4

🤖 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/common/src/io/ktor/server/auth/OAuth2.kt`:
- Around line 79-95: The callback flow in OAuth2.requestToken currently
evaluates extraTokenParametersProvider before state validation, so move state
verification ahead of the dynamic token-parameter hook. Update the request
handling in OAuth2.kt so verifyState is called first using the existing
stateVerifier/settings.verifyState path, then compute extraTokenParameters and
pass them into oauth2RequestAccessToken only after the state has been accepted.

In
`@ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/OAuthFlow.kt`:
- Line 430: The callback routes in OAuthFlow are currently registered with GET
only, so form_post OAuth callbacks are missed even though the flow supports
them. Update the typed callback handlers in OAuthFlow to use a method-agnostic
route(path) with handle { ... } for flow.callback.path, and apply the same
change to the session-backed callback route, while leaving the optional login
route as GET-only.
- Around line 103-110: The validate(flowName) helpers in OAuthFlow are currently
part of the stable public ABI even though they are only used by builder/factory
internals. Change these validation hooks to internal or `@PublishedApi` internal,
and if they must remain callable from outside, annotate them with `@InternalAPI`
instead. Apply the same visibility treatment to the other validate(flowName)
methods mentioned in this diff so the public surface stays intentional.
- Around line 463-467: The OAuth callback flow is writing the session before
principal resolution, which can leave an OAuth session cookie behind even when
principal creation fails. In OAuthFlow’s callback handling, move the
call.sessions.set(...) invocation to after flow.principalResolver(this, session)
succeeds, and keep the failure path in the same block so a null principal
returns via callback.failureHandler without persisting the session.
🪄 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: f45444fd-e1ba-4a81-988b-2b1f4f6b272d

📥 Commits

Reviewing files that changed from the base of the PR and between c916af8 and a801fbf.

📒 Files selected for processing (15)
  • ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.api
  • ktor-server/ktor-server-plugins/ktor-server-auth/api/ktor-server-auth.klib.api
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/AuthenticationInterceptors.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/OAuth2.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/OAuthCommon.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/OAuthProcedure.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/BasicTypedProvider.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/OAuthFlow.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/Scopes.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/SessionAuthScheme.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/SessionTransport.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/SessionsConfigTypesafeExtensions.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/src/io/ktor/server/auth/typesafe/TypedSessionAuthConfig.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/OAuth2Test.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/typesafe/OAuthFlowTest.kt

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.

4 participants