Skip to content

fix(auth_n): try refresh oidc client on token exchange failure#1088

Open
ayushjain17 wants to merge 2 commits into
mainfrom
oidc_client_refresh
Open

fix(auth_n): try refresh oidc client on token exchange failure#1088
ayushjain17 wants to merge 2 commits into
mainfrom
oidc_client_refresh

Conversation

@ayushjain17

@ayushjain17 ayushjain17 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Change log

  1. Try to refresh on every token exchange with OIDC IdP in case the exchange fails and use the refreshed info to re-try token exchange - refresh happens only during the call.

Note: The refresh would keep happening on every fail token exchange if a successful refresh was not achieved. token exchange re-try is only done once, re-try failure will lead to a terminal state in the user login experience avoiding the infinite redirects.

  1. Reduce auth cookie to only contain only id_token
  2. Add support of using id_token as Bearer token value

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added direct token-based authentication support across enabled and disabled modes.
    • Improved OIDC sign-in by refreshing provider metadata and reusing refreshed client configuration.
  • Bug Fixes
    • Prevents login failures caused by stale signing keys by retrying ID token verification once after refresh.
    • Returns a clear “Sign-in failed” unauthorized response when verification still fails.
  • Refactor
    • Streamlined authentication middleware flow for consistent handling of login type and responses.

Copilot AI review requested due to automatic review settings July 9, 2026 11:35
@ayushjain17 ayushjain17 requested a review from a team as a code owner July 9, 2026 11:35
@semanticdiff-com

semanticdiff-com Bot commented Jul 9, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

OIDC authentication now uses refreshable clients and direct ID-token verification. Failed verification triggers one metadata/client refresh and retry. Token-based authentication is supported across authenticators, and middleware authentication control flow is consolidated around a single future.

Changes

OIDC authentication refresh and verification

Layer / File(s) Summary
OIDC contracts, token types, and client helpers
crates/service_utils/src/middlewares/auth_n/oidc/{types,utils}.rs, crates/service_utils/src/middlewares/auth_n/oidc.rs
Updates client and token contracts, adds provider metadata discovery and client construction helpers, and replaces response-oriented token aliases with direct ID-token types.
Refreshable SaaS and Simple authenticators
crates/service_utils/src/middlewares/auth_n/oidc/{saas_authenticator,simple_authenticator}.rs
Stores typed configuration and lock-protected clients, rebuilds clients from refreshed provider metadata, and updates global/org client derivation and verification.
Direct token authentication
crates/service_utils/src/middlewares/auth_n/authentication.rs, crates/service_utils/src/middlewares/auth_n/no_auth.rs, crates/service_utils/src/middlewares/auth_n/oidc/{saas_authenticator,simple_authenticator}.rs
Adds authenticate_with_token to the authenticator contract and implementations, including direct global/org token decoding and user conversion.
Login verification retry flow
crates/service_utils/src/middlewares/auth_n/oidc.rs
Verifies ID tokens after exchange, refreshes and retries once on failure, then returns an Unauthorized sign-in failure response if verification still fails.
Unified middleware authentication flow
crates/service_utils/src/middlewares/auth_n.rs
Computes login type once and awaits one authentication future for authorization-header and fallback paths.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant OidcLogin
  participant OIDCAuthenticator
  participant OidcProvider

  Client->>OidcLogin: authorization code
  OidcLogin->>OidcProvider: exchange code for tokens
  OidcLogin->>OidcLogin: verify_id_token(current client)
  alt verification fails
    OidcLogin->>OIDCAuthenticator: refresh_client()
    OIDCAuthenticator->>OidcProvider: fetch provider metadata
    OIDCAuthenticator->>OIDCAuthenticator: build_client(metadata)
    OidcLogin->>OidcLogin: verify_id_token(refreshed client)
    OidcLogin-->>Client: authenticated result or Unauthorized sign-in failure
  else verification succeeds
    OidcLogin-->>Client: authenticated result
  end
Loading

Poem

A rabbit found a stale key in the hay,
Refreshed the client and hopped on its way.
Tokens were checked, then checked once more,
While locks kept the metadata safe at the door. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: retrying OIDC after client refresh on authentication failure.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch oidc_client_refresh

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the OIDC authentication flow to better tolerate IdP metadata/JWKS changes by allowing the OIDC client (and, for SaaS, provider metadata) to be refreshed at runtime and retrying ID-token verification once after a refresh. It also changes the terminal failure behavior to return an explicit “Sign-in failed” response instead of redirecting again (avoiding redirect loops).

Changes:

  • Add shared helpers to (re)discover provider metadata and rebuild a CoreClient.
  • Store OIDC clients (and SaaS provider metadata) behind locks so they can be swapped on refresh.
  • On ID-token verification failure, refresh provider metadata/client once and retry verification; on repeated failure, return an unauthorized error page.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
crates/service_utils/src/middlewares/auth_n/oidc/utils.rs Adds provider metadata discovery and CoreClient construction helpers for reuse and refresh.
crates/service_utils/src/middlewares/auth_n/oidc/simple_authenticator.rs Switches to a refreshable, lock-protected CoreClient and implements refresh_client().
crates/service_utils/src/middlewares/auth_n/oidc/saas_authenticator.rs Switches to refreshable, lock-protected CoreClient + provider metadata and implements refresh_client().
crates/service_utils/src/middlewares/auth_n/oidc.rs Extends the OIDC authenticator trait with refresh, retries ID-token verification after refresh, and adds a terminal failure response.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +141 to +147
// Verify the freshly-minted ID token. If verification fails, the most
// common cause is that the IdP has rotated its signing keys since we
// last fetched the JWKS, so the cached keys can't validate the new
// token's signature. Refresh the provider metadata once and retry
// before giving up — this self-heals key rotation without a restart.
let mut response =
verify_id_token(&data.get_client(), &token_response, &p_cookie.nonce);

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.

if no id token is there, then shouldn't the person login to get the info

Comment on lines +90 to +95
fn get_client(&self) -> CoreClient {
self.client
.read()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
Comment on lines +225 to +230
fn get_client(&self) -> CoreClient {
self.client
.read()
.unwrap_or_else(|e| e.into_inner())
.clone()
}
Comment on lines +141 to +147
// Verify the freshly-minted ID token. If verification fails, the most
// common cause is that the IdP has rotated its signing keys since we
// last fetched the JWKS, so the cached keys can't validate the new
// token's signature. Refresh the provider metadata once and retry
// before giving up — this self-heals key rotation without a restart.
let mut response =
verify_id_token(&data.get_client(), &token_response, &p_cookie.nonce);

@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.

🧹 Nitpick comments (2)
crates/service_utils/src/middlewares/auth_n/oidc/saas_authenticator.rs (1)

232-253: 🚀 Performance & Scalability | 🔵 Trivial

Consider de-duplicating concurrent refreshes to avoid a discovery-fetch storm during key rotation.

refresh_client is invoked from the login handler on every ID-token verification failure. During a JWKS-rotation window, multiple in-flight logins will each fail verification and independently call refresh_client, issuing N concurrent discovery fetches against the IdP for what is effectively the same refresh. A single-flight guard (collapse concurrent refreshes into one) or a short minimum interval between refreshes would bound the load on the IdP discovery endpoint while preserving the self-heal behavior.

The two-step swap (metadata then client under separate write locks) is fine for the retry path, since both writes are in-memory and no lock is held across the await.

🤖 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 `@crates/service_utils/src/middlewares/auth_n/oidc/saas_authenticator.rs`
around lines 232 - 253, `refresh_client` in `SaasAuthenticator` is doing
redundant concurrent discovery fetches when multiple login attempts fail at the
same time during key rotation. Add a single-flight or other coalescing guard
around `refresh_client` so only one refresh runs per issuer at a time, and have
other callers reuse or await that in-flight refresh instead of calling
`fetch_provider_metadata` repeatedly. Keep the existing two-step swap into
`provider_metadata` and `client` as-is, but make the refresh entrypoint
resilient to concurrent invocations from the login handler.
crates/service_utils/src/middlewares/auth_n/oidc.rs (1)

208-208: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the redundant .map_err(|e| e)
claims(...) already returns ClaimsVerificationError, so the identity map_err adds no value and may trigger clippy::map_identity under -D warnings.

🤖 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 `@crates/service_utils/src/middlewares/auth_n/oidc.rs` at line 208, The
`claims(...)` call in `Oidc` token verification is wrapped with a redundant
`.map_err(|e| e)` identity conversion. Remove that `map_err` from the `and_then`
chain so the result from `client.id_token_verifier()` and `claims(...)` is
passed through directly without triggering the identity-map warning.
🤖 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.

Nitpick comments:
In `@crates/service_utils/src/middlewares/auth_n/oidc.rs`:
- Line 208: The `claims(...)` call in `Oidc` token verification is wrapped with
a redundant `.map_err(|e| e)` identity conversion. Remove that `map_err` from
the `and_then` chain so the result from `client.id_token_verifier()` and
`claims(...)` is passed through directly without triggering the identity-map
warning.

In `@crates/service_utils/src/middlewares/auth_n/oidc/saas_authenticator.rs`:
- Around line 232-253: `refresh_client` in `SaasAuthenticator` is doing
redundant concurrent discovery fetches when multiple login attempts fail at the
same time during key rotation. Add a single-flight or other coalescing guard
around `refresh_client` so only one refresh runs per issuer at a time, and have
other callers reuse or await that in-flight refresh instead of calling
`fetch_provider_metadata` repeatedly. Keep the existing two-step swap into
`provider_metadata` and `client` as-is, but make the refresh entrypoint
resilient to concurrent invocations from the login handler.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2124887-1eaa-46d6-a65a-b70094563ab2

📥 Commits

Reviewing files that changed from the base of the PR and between 77e840c and fdc2937.

📒 Files selected for processing (4)
  • crates/service_utils/src/middlewares/auth_n/oidc.rs
  • crates/service_utils/src/middlewares/auth_n/oidc/saas_authenticator.rs
  • crates/service_utils/src/middlewares/auth_n/oidc/simple_authenticator.rs
  • crates/service_utils/src/middlewares/auth_n/oidc/utils.rs

@ayushjain17 ayushjain17 force-pushed the oidc_client_refresh branch from fdc2937 to af596e7 Compare July 9, 2026 11:55
@ayushjain17

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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 `@crates/service_utils/src/middlewares/auth_n/oidc.rs`:
- Around line 141-161: The retry in the ID token verification flow still uses
the pre-refresh client and stale JWKS. Update verify_id_token to borrow only
token_response from the token response while accepting the client independently,
then re-fetch the client via data.get_client() after a successful
data.refresh_client() before retrying verification.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc09bdfe-6081-454f-8df0-aef64fed821e

📥 Commits

Reviewing files that changed from the base of the PR and between 77e840c and fd669a6.

📒 Files selected for processing (8)
  • crates/service_utils/src/middlewares/auth_n.rs
  • crates/service_utils/src/middlewares/auth_n/authentication.rs
  • crates/service_utils/src/middlewares/auth_n/no_auth.rs
  • crates/service_utils/src/middlewares/auth_n/oidc.rs
  • crates/service_utils/src/middlewares/auth_n/oidc/saas_authenticator.rs
  • crates/service_utils/src/middlewares/auth_n/oidc/simple_authenticator.rs
  • crates/service_utils/src/middlewares/auth_n/oidc/types.rs
  • crates/service_utils/src/middlewares/auth_n/oidc/utils.rs

Comment on lines +141 to +161
let client = data.get_client();

// Verify the freshly-minted ID token. If verification fails, the most
// common cause is that the IdP has rotated its signing keys since we
// last fetched the JWKS, so the cached keys can't validate the new
// token's signature. Refresh the provider metadata once and retry
// before giving up — this self-heals key rotation without a restart.
let mut response = verify_id_token(&client, &token_response, &p_cookie.nonce);
if let Err(e) = &response {
log::error!(
"OIDC: ID token verification failed; refreshing provider keys and retrying, error: {e:?}"
);
match data.refresh_client().await {
Ok(()) => {
response = verify_id_token(&client, &token_response, &p_cookie.nonce);
}
Err(e) => {
log::error!("OIDC: failed to refresh provider metadata: {e}")
}
}
}

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.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the relevant section
sed -n '1,260p' crates/service_utils/src/middlewares/auth_n/oidc.rs

echo '--- search get_client/refresh_client definitions ---'
rg -n "fn get_client|fn refresh_client|struct .*CoreClient|verify_id_token\\(" crates/service_utils/src -S

Repository: juspay/superposition

Length of output: 9310


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  crates/service_utils/src/middlewares/auth_n/oidc/simple_authenticator.rs \
  crates/service_utils/src/middlewares/auth_n/oidc/saas_authenticator.rs
do
  echo "===== $f ====="
  sed -n '1,320p' "$f"
done

Repository: juspay/superposition

Length of output: 18843


Re-read the client before retrying
crates/service_utils/src/middlewares/auth_n/oidc.rs:141-161let client = data.get_client(); captures the pre-refresh CoreClient, so the retry still validates against stale JWKS and won’t recover after key rotation. Re-fetch the client after refresh_client(), and relax verify_id_token so only token_response is tied to the returned token reference.

🤖 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 `@crates/service_utils/src/middlewares/auth_n/oidc.rs` around lines 141 - 161,
The retry in the ID token verification flow still uses the pre-refresh client
and stale JWKS. Update verify_id_token to borrow only token_response from the
token response while accepting the client independently, then re-fetch the
client via data.get_client() after a successful data.refresh_client() before
retrying verification.

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.

2 participants