fix(idp): apply PKCE when building OAuth and OIDC providers (#12247)

# Which Problems Are Solved

- IDP intent authorization redirects used by Login v2 ignored the
configured `UsePKCE` setting for Generic OAuth providers
- This caused providers such as X/Twitter OAuth2 to receive
authorization requests without `code_challenge` and
`code_challenge_method`
- OIDC provider construction had the same gap, even though PKCE is
already part of the provider configuration model
- Existing IDP intent redirect tests expected non-PKCE OAuth URLs and
failed once PKCE was applied correctly

# How the Problems Are Solved

- Updated `OAuthIDPWriteModel.ToProvider` in
internal/command/idp_model.go to pass `rp.WithPKCE(nil)` when `UsePKCE`
is enabled
- Updated `OIDCIDPWriteModel.ToProvider` to apply the same PKCE
relying-party option for OIDC providers

# Additional Changes

- Added focused provider-construction tests covering:
    - OAuth provider redirects include code_challenge
    - OIDC provider redirects include code_challenge
    - both providers use code_challenge_method=S256
    - both persist the generated codeVerifier for token exchange
- Updated `TestCommands_AuthFromProvider` in
internal/command/idp_intent_test.go so OAuth redirect assertions verify
PKCE structurally instead of hard-coding the generated challenge value

# Additional Context

- Reproduced with X/Twitter OAuth2 where the generated authorization URL
was missing PKCE parameters despite `usePkce: true`
- Verified both the focused PKCE tests and the full unit test suite run
successfully with the fix
- Closes #12036 
- Closes/supersedes #12054:
  - Tests are included
  - No slice re-allocation on `opts` append
  
# Result

### Before

`authUrl` in `StartIdentityProviderIntent` response is missing
`code_challenge` and `code_challenge_method` for Generic OAuth IDP with
PKCE enabled:
```json
{
    "details": {
        "sequence": "1",
        "changeDate": "2026-06-07T18:48:16.921317Z",
        "resourceOwner": "376298239768395779"
    },
    "authUrl": "https://x.com/i/oauth2/authorize?client_id=<REDACTED>&prompt=select_account&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fidps%2Fcallback&response_type=code&scope=tweet.read+users.email+users.read+offline.access&state=376417136207200259"
}
```

### After

`code_challenge` and `code_challenge_method` are correctly included into
`authUrl`:

```json
{
    "details": {
        "sequence": "1",
        "changeDate": "2026-06-07T18:49:39.488941Z",
        "resourceOwner": "376298239768395779"
    },
    "authUrl": "https://x.com/i/oauth2/authorize?client_id=<REDACTED>&code_challenge=8G4vN8QNgSsbvGSHKwYPEc2qUYU2BK5L0fsr992duTA&code_challenge_method=S256&prompt=select_account&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fidps%2Fcallback&response_type=code&scope=tweet.read+users.email+users.read+offline.access&state=376417274736672771"
}
```
This commit is contained in:
Evan
2026-06-11 19:52:11 +02:00
committed by GitHub
parent c5463b51ff
commit cf4554b82c
3 changed files with 146 additions and 7 deletions
+8 -2
View File
@@ -179,7 +179,10 @@ func (wm *OAuthIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encry
RedirectURL: callbackURL,
Scopes: wm.Scopes,
}
opts := make([]oauth.ProviderOpts, 0, 4)
opts := make([]oauth.ProviderOpts, 0, 5)
if wm.UsePKCE {
opts = append(opts, oauth.WithRelyingPartyOption(rp.WithPKCE(nil)))
}
if wm.IsCreationAllowed {
opts = append(opts, oauth.WithCreationAllowed())
}
@@ -382,11 +385,14 @@ func (wm *OIDCIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encryp
if err != nil {
return nil, err
}
opts := make([]oidc.ProviderOpts, 1, 6)
opts := make([]oidc.ProviderOpts, 1, 7)
opts[0] = oidc.WithSelectAccount()
if wm.IsIDTokenMapping {
opts = append(opts, oidc.WithIDTokenMapping())
}
if wm.UsePKCE {
opts = append(opts, oidc.WithRelyingPartyOption(rp.WithPKCE(nil)))
}
if wm.IsCreationAllowed {
opts = append(opts, oidc.WithCreationAllowed())
}