Skip to content

Add OIDC provider lifecycle#5637

Open
Pantus Oleh (zibet27) wants to merge 1 commit into
zibet27/oidc-discoveryfrom
zibet27/oidc-provider-lifecycle
Open

Add OIDC provider lifecycle#5637
Pantus Oleh (zibet27) wants to merge 1 commit into
zibet27/oidc-discoveryfrom
zibet27/oidc-provider-lifecycle

Conversation

@zibet27

Copy link
Copy Markdown
Collaborator

Subsystem
Server Auth

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

Solution
Adds the base OpenID Connect plugin lifecycle, but no authentication flows yet.
This introduces

  • provider registration through openIdConnect
  • issuer-based provider configuration
  • metadata discovery on startup
  • periodic metadata refresh
  • environment-based issuer defaults
  • exposes OidcProvider.currentMetadata() for consumers that need the active provider metadata

Authentication, bearer token validation, OAuth callback handling, UserInfo, PKCE, sessions/logout, introspection, and 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: 7c5e4f86-f30d-4b22-985c-9dde9c8a9182

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 adds a complete OpenID Connect (OIDC) server authentication plugin to Ktor. The implementation includes provider registration with validation, initial discovery with configurable retry logic, periodic metadata refresh with event publishing, comprehensive configuration models, and full test coverage.

Changes

OIDC Plugin Implementation and Tests

Layer / File(s) Summary
Configuration Models
ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcConfig.kt, build.gradle.kts
OidcPluginConfig defines HTTP client, discovery refresh intervals, and retry parameters; OidcProviderConfig holds per-provider issuer configuration; both include validation methods. Gradle dependency adds projects.ktorServerCore to api configuration.
Discovery Verification and Provider State
OidcDiscovery.kt, OidcProvider.kt
HttpClient.discoverVerified() fetches metadata and validates issuer field matches exactly. OidcProvider maintains volatile metadata state with updateMetadata() and currentMetadata() accessors with initialization guards.
Core Plugin Registration, Discovery, and Refresh
Oidc.kt (impl lines 1–184)
Oidc plugin class manages concurrent provider registration with name/issuer validation, initial discovery with configurable retry attempts and failure propagation, and periodic background metadata refresh. Refresh failures trigger OidcMetadataRefreshFailed event while retaining last successful metadata.
Event Types and Plugin Installation
Oidc.kt (install/events lines 186–279)
OidcMetadataRefreshFailure event payload with causedByValidation derived from cause type; EventDefinition for monitor subscriptions. Plugin installation via BaseApplicationPlugin merges environment and user configuration, manages HTTP client lifecycle, warns if no providers configured. Application.openIdConnect() overloads provide convenient setup.
Public API Export
api/ktor-server-auth-openid.api
Declared public API includes Oidc plugin, OidcProvider, OidcMetadataRefreshFailure, OidcPluginConfig, OidcProviderConfig, and installation helpers.
Test Infrastructure
utils/OidcTestUtils.kt, utils/OidcTestProviders.kt
Test utilities define ISSUER_URL constant, lenient JSON serializer, HTTP client factories for no-redirect and JSON-configured clients. TestApplicationBuilder.openIdProvider() overloads register issuer routing; Route.openIdDiscoveryEndpoint() serves /.well-known/openid-configuration.
Discovery Behavior Tests
OidcDiscoveryTest.kt
Six test cases verify periodic metadata refresh updates provider state, failed refresh publishes event and retains stale metadata, initial discovery retries non-issuer errors, throws after exhausted attempts, releases state after failure, and rejects non-matching issuer without retry. Includes helpers for mocked discovery endpoint.
Provider Registration and Synchronization Tests
OidcPluginRegistrationTest.kt
Three test cases verify OidcProvider metadata exposure and plugin singleton behavior, concurrent registration synchronization for duplicate names (synchronized with expected failures) and distinct names (succeeds independently), and provider name format validation with duplicate detection within single Oidc instance. Includes helpers for concurrent assertion.
Environment Configuration Tests
OidcEnvironmentConfigTest.kt
Three test cases verify environment-based provider configuration is deferred until registered in code, issuer can be extended after code registration, and validation failure does not consume environment config.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.26% 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 provider lifecycle' directly and clearly describes the main change in the PR, which introduces the base OpenID Connect plugin lifecycle with provider registration, discovery, and metadata refresh.
Description check ✅ Passed The PR description follows the repository template with all required sections (Subsystem, Motivation, Solution) completed. It provides clear context about the OIDC feature addition and scope limitations.
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-provider-lifecycle

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

🤖 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/OidcConfig.kt`:
- Around line 80-87: Update the OidcConfig.validate() function to also validate
discoveryRefreshFailureDelay by requiring
discoveryRefreshFailureDelay.isFinite() and
discoveryRefreshFailureDelay.isPositive(), and update the existing require error
messages for initialDiscoveryAttempts and initialDiscoveryRetryDelay (and the
new discoveryRefreshFailureDelay check) to include the actual invalid value for
easier debugging; reference the validate() method and the
discoveryRefreshFailureDelay property (and
initialDiscoveryAttempts/initialDiscoveryRetryDelay) so the change prevents
immediate retry loops observed in the while(isActive) loop in Oidc.kt.
🪄 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: f1bbf002-437c-4872-934a-f351e229a7fb

📥 Commits

Reviewing files that changed from the base of the PR and between 6efbb87 and 5b3c889.

📒 Files selected for processing (11)
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/api/ktor-server-auth-openid.api
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/build.gradle.kts
  • 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/OidcConfig.kt
  • ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcDiscovery.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/test/io/ktor/server/auth/openid/OidcDiscoveryTest.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/OidcPluginRegistrationTest.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 +80 to +87
internal fun validate() {
require(initialDiscoveryAttempts >= 1) {
"initialDiscoveryAttempts must be greater than or equal to 1"
}
require(initialDiscoveryRetryDelay.isFinite() && !initialDiscoveryRetryDelay.isNegative()) {
"initialDiscoveryRetryDelay must be finite and non-negative"
}
}

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

🧩 Analysis chain

🌐 Web query:

In kotlinx.coroutines, what is the behavior of delay(Duration.ZERO) and delay(negative Duration), and can this create immediate retry loops in while(isActive) polling code?

💡 Result:

In kotlinx.coroutines, the delay function behaves as follows when provided with non-positive durations: - Behavior: If the duration passed to delay is Duration.ZERO or a negative Duration, the function returns immediately without suspending the coroutine [1]. - Cancellation Check: Because the function returns immediately without entering a suspended state, it does not perform a cancellation check [2]. The prompt cancellation guarantee provided by delay only applies when the coroutine actually suspends [2][1]. - Impact on Polling Loops: This behavior can indeed create immediate retry loops in while(isActive) polling code [2]. If a loop relies on delay(duration) to yield control and check for cancellation, passing a non-positive duration causes the loop to execute continuously without suspension. This prevents the coroutine from yielding to other tasks and, crucially, prevents it from noticing that its job has been cancelled, potentially leading to an infinite loop or excessive resource consumption [2]. To ensure proper cancellation and yielding in such loops, you should explicitly check for cancellation (e.g., by using yield or ensuring the loop condition is robust) or ensure that the delay duration is always positive [2].

Citations:


🏁 Script executed:

# First, let's examine the OidcConfig.kt file to understand the context
wc -l ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcConfig.kt

Repository: ktorio/ktor

Length of output: 164


🏁 Script executed:

# Read the OidcConfig.kt file to understand the properties and validate() method
cat -n ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/OidcConfig.kt

Repository: ktorio/ktor

Length of output: 4665


🏁 Script executed:

# Search for discoveryRefreshFailureDelay usage in the codebase
rg "discoveryRefreshFailureDelay" -B 3 -A 3

Repository: ktorio/ktor

Length of output: 4331


🏁 Script executed:

# Let's find and examine the Oidc.kt file to see how discoveryRefreshFailureDelay is used
fd "Oidc.kt" -t f | head -5

Repository: ktorio/ktor

Length of output: 154


🏁 Script executed:

# Read the Oidc.kt file to see the retry loop context
cat -n ktor-server/ktor-server-plugins/ktor-server-auth-openid/jvm/src/io/ktor/server/auth/openid/Oidc.kt | grep -A 20 -B 5 "while (isActive)"

Repository: ktorio/ktor

Length of output: 1370


Add validation for discoveryRefreshFailureDelay to prevent immediate retry loops.

The validate() method does not validate discoveryRefreshFailureDelay. If set to Duration.ZERO or negative, delay() will return immediately without suspending, causing immediate retries in the while(isActive) loop (Oidc.kt) and flooding discovery requests.

Add a require check for discoveryRefreshFailureDelay with isFinite() and isPositive(), and improve all error messages to include the actual value for better debugging.

Suggested fix
 internal fun validate() {
     require(initialDiscoveryAttempts >= 1) {
-        "initialDiscoveryAttempts must be greater than or equal to 1"
+        "initialDiscoveryAttempts must be greater than or equal to 1, but was $initialDiscoveryAttempts"
+    }
+    require(discoveryRefreshFailureDelay.isFinite() && discoveryRefreshFailureDelay.isPositive()) {
+        "discoveryRefreshFailureDelay must be finite and positive, but was $discoveryRefreshFailureDelay"
     }
     require(initialDiscoveryRetryDelay.isFinite() && !initialDiscoveryRetryDelay.isNegative()) {
-        "initialDiscoveryRetryDelay must be finite and non-negative"
+        "initialDiscoveryRetryDelay must be finite and non-negative, but was $initialDiscoveryRetryDelay"
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
internal fun validate() {
require(initialDiscoveryAttempts >= 1) {
"initialDiscoveryAttempts must be greater than or equal to 1"
}
require(initialDiscoveryRetryDelay.isFinite() && !initialDiscoveryRetryDelay.isNegative()) {
"initialDiscoveryRetryDelay must be finite and non-negative"
}
}
internal fun validate() {
require(initialDiscoveryAttempts >= 1) {
"initialDiscoveryAttempts must be greater than or equal to 1, but was $initialDiscoveryAttempts"
}
require(discoveryRefreshFailureDelay.isFinite() && discoveryRefreshFailureDelay.isPositive()) {
"discoveryRefreshFailureDelay must be finite and positive, but was $discoveryRefreshFailureDelay"
}
require(initialDiscoveryRetryDelay.isFinite() && !initialDiscoveryRetryDelay.isNegative()) {
"initialDiscoveryRetryDelay must be finite and non-negative, but was $initialDiscoveryRetryDelay"
}
}
🤖 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 80 - 87, Update the OidcConfig.validate() function to also validate
discoveryRefreshFailureDelay by requiring
discoveryRefreshFailureDelay.isFinite() and
discoveryRefreshFailureDelay.isPositive(), and update the existing require error
messages for initialDiscoveryAttempts and initialDiscoveryRetryDelay (and the
new discoveryRefreshFailureDelay check) to include the actual invalid value for
easier debugging; reference the validate() method and the
discoveryRefreshFailureDelay property (and
initialDiscoveryAttempts/initialDiscoveryRetryDelay) so the change prevents
immediate retry loops observed in the while(isActive) loop in Oidc.kt.

@e5l Leonid Stashevsky (e5l) removed their request for review June 1, 2026 08:59
Base automatically changed from zibet27/test-host-external-services-startup to zibet27/oidc-discovery June 5, 2026 12:29
# Conflicts:
#	ktor-server/ktor-server-plugins/ktor-server-auth-oidc/api/ktor-server-auth-oidc.api
#	ktor-server/ktor-server-plugins/ktor-server-auth-oidc/jvm/test/io/ktor/server/auth/oidc/FetchOpenIdProviderMetadataTest.kt
@zibet27 Pantus Oleh (zibet27) force-pushed the zibet27/oidc-provider-lifecycle branch from 6767cec to 8faf94e Compare June 10, 2026 14:26
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