add support for response_mode=form_post

This commit is contained in:
John van der Wulp
2026-03-04 13:21:44 +01:00
parent 27ca713cd4
commit 1ea5eec7d2
7 changed files with 86 additions and 11 deletions

View File

@@ -91,6 +91,15 @@ func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc) e
}
if path == "index.html" {
// Check if this is an OAuth2 authorization request with response_mode=form_post
// In that case, we need to allow form submissions to the redirect_uri
responseMode := c.Query("response_mode")
redirectURI := c.Query("redirect_uri")
if responseMode == "form_post" && redirectURI != "" {
// Set the allowed form-action in CSP to include the redirect URI
middleware.SetAllowedFormAction(c, redirectURI)
}
nonce := middleware.GetCSPNonce(c)
// Do not cache the HTML shell, as it embeds a per-request nonce

View File

@@ -94,6 +94,11 @@ func (oc *OidcController) authorizeHandler(c *gin.Context) {
return
}
// Set the allowed form-action in CSP when response_mode is form_post
if input.ResponseMode == "form_post" && input.CallbackURL != "" {
middleware.SetAllowedFormAction(c, input.CallbackURL)
}
code, callbackURL, err := oc.oidcService.Authorize(c.Request.Context(), input, c.GetString("userID"), c.ClientIP(), c.Request.UserAgent())
if err != nil {
_ = c.Error(err)

View File

@@ -71,6 +71,7 @@ type AuthorizeOidcClientRequestDto struct {
CodeChallenge string `json:"codeChallenge"`
CodeChallengeMethod string `json:"codeChallengeMethod"`
ReauthenticationToken string `json:"reauthenticationToken"`
ResponseMode string `json:"responseMode"`
}
type AuthorizeOidcClientResponseDto struct {

View File

@@ -23,17 +23,39 @@ func GetCSPNonce(c *gin.Context) string {
return ""
}
// SetAllowedFormAction sets the allowed form-action for the CSP header.
// This is used for OAuth2 response_mode=form_post to allow form submissions to the client's redirect URI.
func SetAllowedFormAction(c *gin.Context, uri string) {
c.Set("csp_allowed_form_action", uri)
}
func (m *CspMiddleware) Add() gin.HandlerFunc {
return func(c *gin.Context) {
// Generate a random base64 nonce for this request
nonce := generateNonce()
c.Set("csp_nonce", nonce)
// Get any allowed form-action from context (set by the authorize endpoint)
// Also check query parameters for response_mode=form_post (for both frontend and API requests)
formAction := "'self'"
if v, ok := c.Get("csp_allowed_form_action"); ok {
if uri, ok := v.(string); ok && uri != "" {
formAction = "'self' " + uri
}
} else {
// If not set by the authorize endpoint, check query parameters
responseMode := c.Query("response_mode")
redirectURI := c.Query("redirect_uri")
if responseMode == "form_post" && redirectURI != "" {
formAction = "'self' " + redirectURI
}
}
csp := "default-src 'self'; " +
"base-uri 'self'; " +
"object-src 'none'; " +
"frame-ancestors 'none'; " +
"form-action 'self'; " +
"form-action " + formAction + "; " +
"img-src * blob:;" +
"font-src 'self'; " +
"style-src 'self' 'unsafe-inline'; " +