Implement PKCE (Proof Key for Code Exchange) support

PKCE was advertised in the discovery document but not actually implemented.
This commit adds full PKCE support:

- Store code_challenge and code_challenge_method in authorization code JWT
- Accept code_verifier parameter in token endpoint
- Validate code_verifier against stored code_challenge
- Support both S256 (SHA256) and plain code challenge methods
- PKCE validation is required when code_challenge is present

This prevents authorization code interception attacks by requiring
the client to prove possession of the code_verifier that was used
to generate the code_challenge.
This commit is contained in:
Olivier Dumont
2025-12-30 12:39:00 +01:00
parent ef157ae9ba
commit dabb4398ad
2 changed files with 89 additions and 31 deletions

View File

@@ -2,7 +2,6 @@ package controller
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
@@ -143,8 +142,8 @@ func (controller *OIDCController) authorizeHandler(c *gin.Context) {
return
}
// Generate authorization code
authCode, err := controller.oidc.GenerateAuthorizationCode(&userContext, clientID, redirectURI, scopes, nonce)
// Generate authorization code (including PKCE challenge if provided)
authCode, err := controller.oidc.GenerateAuthorizationCode(&userContext, clientID, redirectURI, scopes, nonce, codeChallenge, codeChallengeMethod)
if err != nil {
log.Error().Err(err).Msg("Failed to generate authorization code")
controller.redirectError(c, redirectURI, state, "server_error", "Internal server error")
@@ -223,14 +222,29 @@ func (controller *OIDCController) tokenHandler(c *gin.Context) {
return
}
// Get code_verifier for PKCE validation
codeVerifier := c.PostForm("code_verifier")
if codeVerifier == "" {
codeVerifier = c.Query("code_verifier")
}
// Validate authorization code
userContext, scopes, nonce, err := controller.oidc.ValidateAuthorizationCode(code, clientID, redirectURI)
userContext, scopes, nonce, codeChallenge, codeChallengeMethod, err := controller.oidc.ValidateAuthorizationCode(code, clientID, redirectURI)
if err != nil {
log.Error().Err(err).Msg("Failed to validate authorization code")
controller.tokenError(c, "invalid_grant", "Invalid or expired authorization code")
return
}
// Validate PKCE if code challenge was provided
if codeChallenge != "" {
if err := controller.oidc.ValidatePKCE(codeChallenge, codeChallengeMethod, codeVerifier); err != nil {
log.Error().Err(err).Msg("PKCE validation failed")
controller.tokenError(c, "invalid_grant", "Invalid code_verifier")
return
}
}
// Generate tokens
accessToken, err := controller.oidc.GenerateAccessToken(userContext, clientID, scopes)
if err != nil {