Skip to content

feat: support for the prompt parameter in the oidc flow#948

Open
steveiliop56 wants to merge 5 commits into
mainfrom
feat/oidc-prompt
Open

feat: support for the prompt parameter in the oidc flow#948
steveiliop56 wants to merge 5 commits into
mainfrom
feat/oidc-prompt

Conversation

@steveiliop56

@steveiliop56 steveiliop56 commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added support for the OpenID Connect prompt parameter (none/login) and propagated it through URL parameters, UI flow, and backend authorization.
    • ID tokens now include auth_time.
  • Improvements

    • prompt=none authorization now returns a standardized “login required” error when the user can’t be silently authenticated.
    • If prompt=none is combined with other prompt values, the request is rejected as invalid.
    • Updated login/authorize page behavior for redirect/auto-authorization and disabled actions during auto-authorization.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 19, 2026
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e1506679-a7a5-4c87-8fcb-8143c0a507cd

📥 Commits

Reviewing files that changed from the base of the PR and between b6f6303 and dcec803.

📒 Files selected for processing (1)
  • internal/controller/oidc_controller.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/controller/oidc_controller.go

📝 Walkthrough

Walkthrough

Adds auth_time to OIDC ID tokens by threading session creation time through UserContext, AuthorizeCodeEntry, and ClaimSet. Implements prompt parameter support in the OIDC authorize endpoint: prompt=none returns login_required for unauthenticated users, and prompt=login sets an oidc_prompt flag forwarded to the frontend, which uses it to control auto-authorization in authorize-page and suppress login-page redirect behavior in login-page.

Changes

OIDC prompt support, auth_time claim, and oidc_prompt frontend parameter

Layer / File(s) Summary
auth_time data model and ID token claim
internal/model/context.go, internal/service/oidc_service.go
UserContext gains AuthTime from session.CreatedAt; AuthorizeCodeEntry and ClaimSet are extended to carry and embed auth_time; generateIDToken accepts an authTime argument; GenerateAccessToken signature adds authTime; RefreshAccessToken passes 0.
prompt parameter types and parsing
internal/service/oidc_service.go
OIDCPrompt type with login/none constants and SupportedPrompts list; AuthorizeRequest gains Prompt field; DecodeAuthorizeJWT extracts prompt and new GetPrompt helper selects supported prompts from space-delimited list.
authorize endpoint prompt handling and auth_time persistence
internal/service/oidc_service.go, internal/controller/oidc_controller.go
AuthorizeCodeEntry gains AuthTime field set during code creation; AuthorizeScreenParams gains OIDCPrompt field; authorize endpoint loads UserContext to reject prompt=none for unauthenticated users with login_required error, includes effective prompt in redirect query; authorizeComplete standardizes UserContext error handling; Token passes entry.AuthTime to GenerateAccessToken.
Frontend oidc_prompt parameter and authorization flow
frontend/src/lib/hooks/screen-params.ts, frontend/src/pages/authorize-page.tsx, frontend/src/pages/login-page.tsx
ScreenParams and zodScreenParams gain optional oidc_prompt field; authorize-page computes shouldAutoAuthorize when authenticated with oidc_prompt=none and required ticket/scope, uses useEffect to auto-invoke authorization mutation, and disables buttons; login-page recompiles params with oidc_prompt cleared and suppresses authenticated-user redirect when oidc_prompt=login; both pages handle login redirect based on oidc_prompt value.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant authorize as /oidc/authorize
  participant AuthorizePage
  participant LoginPage
  participant authorizeComplete as /oidc/authorize-complete
  
  Client->>authorize: GET with prompt=login or prompt=none
  alt prompt=none and unauthenticated
    authorize-->>Client: redirect callback with login_required + state
  else prompt=login
    authorize->>LoginPage: redirect /login?oidc_prompt=login
    LoginPage->>LoginPage: suppress post-auth redirect
    LoginPage->>authorizeComplete: proceed after login with oidc_prompt=undefined
    authorizeComplete->>Client: issue token with auth_time claim
  else prompt=none and authenticated
    authorize->>AuthorizePage: redirect /authorize?oidc_prompt=none&oidc_ticket=...
    AuthorizePage->>AuthorizePage: shouldAutoAuthorize=true
    AuthorizePage->>authorizeComplete: useEffect invokes authorization mutation
    authorizeComplete->>Client: issue token with auth_time claim
  else normal flow
    authorize->>LoginPage: redirect to /login
    LoginPage->>authorizeComplete: proceed with oidc_prompt cleared
    authorizeComplete->>Client: issue token with auth_time claim
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

  • tinyauthapp/tinyauth#686: Both PRs modify the GenerateAccessToken call and signature in oidc_controller.go / oidc_service.go; the earlier PR introduced the code-entry object pattern that this PR extends with authTime.
  • tinyauthapp/tinyauth#907: Both PRs modify internal/controller/oidc_controller.go's /authorize flow by changing how errors are constructed and returned from the authorization handler.
  • tinyauthapp/tinyauth#923: Both PRs modify AuthorizeScreenParams and the OIDC frontend authorize flow; this PR adds OIDCPrompt on top of the ticket-based AuthorizeScreenParams struct refactored there.

Suggested reviewers

  • scottmckendry
  • Rycochet

🐇 A session was born with a tick of the clock,
auth_time now rides in every JWT block.
With prompt=login a flag hops along,
oidc_prompt keeps the auth flow from going wrong.
The bunny checks none, rejects with a hop,
Auto-auth tidy — what a fine crop! 🥕

🚥 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 accurately describes the main feature being added: support for the OIDC prompt parameter across frontend and backend components.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oidc-prompt

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.

@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 13.55932% with 51 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/controller/oidc_controller.go 17.07% 28 Missing and 6 partials ⚠️
internal/service/oidc_service.go 0.00% 17 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread frontend/src/pages/login-page.tsx

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/service/oidc_service.go (1)

618-628: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persist auth_time for refresh-issued ID tokens.

GenerateAccessToken creates the OIDC session without saving codeEntry.AuthTime, so RefreshAccessToken has to call generateIDToken(..., 0) and refreshed ID tokens omit the claim. Store AuthTime with the OIDC session and reuse it during refresh so clients don’t lose the auth_time contract after the initial code exchange.

Also applies to: 666-668

🤖 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 `@internal/service/oidc_service.go` around lines 618 - 628, The
CreateOIDCSession call is not persisting the AuthTime from codeEntry, causing
refreshed ID tokens to lose the auth_time claim. Add the AuthTime field to the
CreateOIDCSessionParams struct and pass codeEntry.AuthTime when creating the
OIDC session. Then update RefreshAccessToken to retrieve the stored AuthTime
from the OIDC session and pass it to generateIDToken instead of passing 0,
ensuring the auth_time claim is preserved across token refreshes.
internal/controller/oidc_controller.go (1)

179-219: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep authenticated prompt=none requests non-interactive.

After the unauthenticated check, an authenticated prompt=none request still creates a frontend ticket and redirects to /oidc/authorize, which requires user interaction. Silent OIDC checks should either complete server-side and redirect with a code, or return an OIDC error such as interaction_required/consent_required.

🤖 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 `@internal/controller/oidc_controller.go` around lines 179 - 219, After the
initial check for unauthenticated users with `prompt=none`, add additional logic
to handle authenticated users who also have `prompt=none` set. When an
authenticated user requests authorization with `prompt=none`, the request should
not proceed to create an authorize ticket and redirect to the interactive
`/oidc/authorize` screen. Instead, handle it non-interactively by either
completing the authorization server-side and redirecting with an authorization
code, or returning an OIDC error such as `interaction_required` or
`consent_required` via the controller.authorizeError method. Add this check
immediately after the existing `prompt=none` and unauthenticated validation and
before the ticket creation logic.
🤖 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 `@internal/controller/oidc_controller.go`:
- Around line 179-201: The current code uses exact string comparisons for
req.Prompt ("none" and "login"), but the OIDC prompt parameter is
space-delimited and can contain multiple values. Parse req.Prompt using
strings.Fields to extract individual tokens, then validate that if "none" is
present it cannot be combined with other values, and branch on token membership
instead of exact string equality. Update the condition checking req.Prompt ==
"none" to check if "none" exists in the parsed tokens, update the condition
checking req.Prompt == "login" to check if "login" exists in the parsed tokens,
and add validation to reject invalid combinations where "none" appears with
other values.

---

Outside diff comments:
In `@internal/controller/oidc_controller.go`:
- Around line 179-219: After the initial check for unauthenticated users with
`prompt=none`, add additional logic to handle authenticated users who also have
`prompt=none` set. When an authenticated user requests authorization with
`prompt=none`, the request should not proceed to create an authorize ticket and
redirect to the interactive `/oidc/authorize` screen. Instead, handle it
non-interactively by either completing the authorization server-side and
redirecting with an authorization code, or returning an OIDC error such as
`interaction_required` or `consent_required` via the controller.authorizeError
method. Add this check immediately after the existing `prompt=none` and
unauthenticated validation and before the ticket creation logic.

In `@internal/service/oidc_service.go`:
- Around line 618-628: The CreateOIDCSession call is not persisting the AuthTime
from codeEntry, causing refreshed ID tokens to lose the auth_time claim. Add the
AuthTime field to the CreateOIDCSessionParams struct and pass codeEntry.AuthTime
when creating the OIDC session. Then update RefreshAccessToken to retrieve the
stored AuthTime from the OIDC session and pass it to generateIDToken instead of
passing 0, ensuring the auth_time claim is preserved across token refreshes.
🪄 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 Plus

Run ID: 61178aab-f39a-4d47-813d-49d5d0f5193b

📥 Commits

Reviewing files that changed from the base of the PR and between 6ccc894 and 80bb4f1.

📒 Files selected for processing (6)
  • frontend/src/lib/hooks/screen-params.ts
  • frontend/src/pages/authorize-page.tsx
  • frontend/src/pages/login-page.tsx
  • internal/controller/oidc_controller.go
  • internal/model/context.go
  • internal/service/oidc_service.go

Comment thread internal/controller/oidc_controller.go Outdated
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jun 19, 2026

@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 `@internal/service/oidc_service.go`:
- Around line 949-965: The GetPrompt function does not validate the OIDC Core
spec requirement that prompt=none cannot be combined with other prompt values.
Add validation logic within the GetPrompt function to detect when the input
contains "none" alongside other prompts and reject this invalid combination.
After splitting the prompt string, check if both "none" and other supported
prompts are present in the list, and if so, return an error indicator or
empty/invalid state rather than proceeding to return the first supported prompt.
This ensures requests like "prompt=none login" are properly rejected as
invalid_request instead of being accepted.
🪄 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 Plus

Run ID: 21f44d18-3908-4622-a031-980dc2766de5

📥 Commits

Reviewing files that changed from the base of the PR and between 80bb4f1 and 32e899e.

📒 Files selected for processing (5)
  • frontend/src/lib/hooks/screen-params.ts
  • frontend/src/pages/authorize-page.tsx
  • frontend/src/pages/login-page.tsx
  • internal/controller/oidc_controller.go
  • internal/service/oidc_service.go

Comment on lines +949 to +965

// Return the first prompt in the list of prompts, or an empty string if no prompt is specified
func (service *OIDCService) GetPrompt(prompt string) OIDCPrompt {
if prompt == "" {
return ""
}

prompts := strings.Split(prompt, " ")

for _, p := range prompts {
if slices.Contains(SupportedPrompts, p) {
return OIDCPrompt(p)
}
}

return ""
}

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

Missing validation for prompt=none combined with other values.

Per OIDC Core spec section 3.1.2.1, prompt=none "MUST NOT be present with any other value." The current implementation returns the first supported prompt without validating this constraint. A request like prompt=none login would be accepted (returning "none"), but should be rejected as invalid_request.

Consider adding validation logic:

Suggested fix
 func (service *OIDCService) GetPrompt(prompt string) OIDCPrompt {
 	if prompt == "" {
 		return ""
 	}

 	prompts := strings.Split(prompt, " ")

+	// OIDC spec: prompt=none MUST NOT be combined with other values
+	if slices.Contains(prompts, string(OIDCPromptNone)) && len(prompts) > 1 {
+		return "" // Or return a special error indicator
+	}
+
 	for _, p := range prompts {
 		if slices.Contains(SupportedPrompts, p) {
 			return OIDCPrompt(p)
 		}
 	}

 	return ""
 }

Alternatively, return an error from this function, or add a separate ValidatePrompt function that the controller calls to reject invalid combinations with an invalid_request error response.

📝 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
// Return the first prompt in the list of prompts, or an empty string if no prompt is specified
func (service *OIDCService) GetPrompt(prompt string) OIDCPrompt {
if prompt == "" {
return ""
}
prompts := strings.Split(prompt, " ")
for _, p := range prompts {
if slices.Contains(SupportedPrompts, p) {
return OIDCPrompt(p)
}
}
return ""
}
// Return the first prompt in the list of prompts, or an empty string if no prompt is specified
func (service *OIDCService) GetPrompt(prompt string) OIDCPrompt {
if prompt == "" {
return ""
}
prompts := strings.Split(prompt, " ")
// OIDC spec: prompt=none MUST NOT be combined with other values
if slices.Contains(prompts, string(OIDCPromptNone)) && len(prompts) > 1 {
return "" // Or return a special error indicator
}
for _, p := range prompts {
if slices.Contains(SupportedPrompts, p) {
return OIDCPrompt(p)
}
}
return ""
}
🤖 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 `@internal/service/oidc_service.go` around lines 949 - 965, The GetPrompt
function does not validate the OIDC Core spec requirement that prompt=none
cannot be combined with other prompt values. Add validation logic within the
GetPrompt function to detect when the input contains "none" alongside other
prompts and reject this invalid combination. After splitting the prompt string,
check if both "none" and other supported prompts are present in the list, and if
so, return an error indicator or empty/invalid state rather than proceeding to
return the first supported prompt. This ensures requests like "prompt=none
login" are properly rejected as invalid_request instead of being accepted.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants