feat(oidc): outbound PKCE + configurable prompt parameter#166
Open
rtjdamen wants to merge 2 commits into
Open
Conversation
OAuth 2.1-compliant IdPs (e.g. Duo SSO) reject /authorize requests that omit code_challenge + code_challenge_method, even from confidential clients. Without this, configuring such an IdP results in: "Must specify both 'code_challenge' and 'code_challenge_method'." Generate a verifier in AuthCodeURL, store it keyed by state, and send the matching code_verifier on Exchange. Verifiers live in an in-process sync.Map; this is acceptable because the proxy already requires sticky sessions for inbound OAuth state. A later change could persist verifiers via the repository abstraction if the proxy is run multi-instance behind a load balancer.
Add --oidc-prompt / OIDC_PROMPT to control the value sent as the OIDC `prompt` query parameter on the upstream /authorize call. Useful for IdPs (e.g. Microsoft Entra ID) where silent SSO would otherwise hide whether MFA actually fired. Setting OIDC_PROMPT=login forces the IdP to render its login screen on every authentication, giving operators visible confirmation that the auth path is exercised. Other accepted values per OIDC Core 1.0 section 3.1.2.1: - login force re-authentication - consent force user consent - select_account let user choose an account - none never prompt (errors if not already authenticated)
Member
|
doc-check has already been removed, so the failure can be ignored. |
hrntknr
requested changes
May 27, 2026
hrntknr
left a comment
Member
There was a problem hiding this comment.
The oidcPrompt approach seems promising. If you specifically want to incorporate oidcPrompt first, we can handle it in a separate PR - we should be able to integrate it quickly. (If you're not in a rush, the current approach is fine as well.)
| // upstream IdP, and consumed in Exchange to fulfill the PKCE flow. | ||
| // Required for OAuth 2.1-compliant IdPs (e.g. Duo SSO) that reject | ||
| // authorization requests without code_challenge. | ||
| pkceVerifiers sync.Map |
Member
There was a problem hiding this comment.
Information about users who exited the login flow remains stored in memory indefinitely. It should be managed similarly to OAuth state, using session storage or similar mechanisms.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related additions to the OIDC client side of the proxy, both surfaced
by trying to use the proxy against IdPs that follow OAuth 2.1 strictly
(in our case Duo SSO and Microsoft Entra ID).
1. Outbound PKCE
OAuth 2.1-compliant IdPs reject
/authorizerequests that omitcode_challenge+code_challenge_method, even from confidentialclients with a client_secret. Without this patch, configuring such an
IdP fails immediately with errors like:
AuthCodeURLnow generates a verifier, stores it keyed bystate,and adds the S256 challenge to the redirect.
Exchangereads theverifier back via the same
statekey and includes it on the tokenexchange. Storage is an in-process
sync.Map— acceptable because theproxy already requires sticky sessions for inbound OAuth state. If
running multi-instance behind a load balancer is on the roadmap, the
verifiers could move into the repository abstraction.
2.
--oidc-prompt/OIDC_PROMPTSends the value as the OIDC
promptquery parameter on the upstream/authorizecall. Empty (default) preserves current behavior.Concrete reason this is useful: with Microsoft Entra ID, an existing
browser session silently SSOs through the redirect without ever showing
the IdP's login screen — making it hard to verify operationally whether
MFA actually fires (it did, on the original session, but it's invisible).
OIDC_PROMPT=loginforces Entra to render the login page on everypairing;
consent/select_account/noneare also acceptedper OIDC Core 1.0 § 3.1.2.1.
Test plan
patch,
/authorizereturns the "Must specify code_challenge"error; with the patch, the flow completes.
OIDC_PROMPT,silent SSO bypasses the login screen; with
OIDC_PROMPT=login,Entra renders the login page on every pairing.
oidc_test.gofor the new fields and I'll happily add it.Compatibility
OIDC_PROMPTset: no behavior change.Auth0 with default config): no behavior change beyond a (harmless)
extra
code_challengeon the wire, which they'll accept.Happy to split this into two PRs if that's preferable — they're
functionally independent. Kept together because they're both very small
and were debugged in the same session.