Parse OAuth Authorization header when request omits client secret (#21351) (#21374)

Backport #21351

This fixes error "unauthorized_client: invalid client secret" when
client includes secret in Authorization header rather than request body.
OAuth spec permits both:
https://www.rfc-editor.org/rfc/rfc6749#section-2.3.1

Clients in possession of a client password MAY use the HTTP Basic
authentication scheme ... Alternatively, the authorization server MAY
support including the client credentials in the request-body

Sanity validation that client id and client secret in request are
consistent with Authorization header.

Improve error descriptions. Error codes remain the same.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
M Hickford 2022-10-08 09:53:17 +01:00 committed by GitHub
parent 672d54fafa
commit 14bc4d79c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 21 additions and 2 deletions

View File

@ -588,7 +588,8 @@ func OIDCKeys(ctx *context.Context) {
// AccessTokenOAuth manages all access token requests by the client // AccessTokenOAuth manages all access token requests by the client
func AccessTokenOAuth(ctx *context.Context) { func AccessTokenOAuth(ctx *context.Context) {
form := *web.GetForm(ctx).(*forms.AccessTokenForm) form := *web.GetForm(ctx).(*forms.AccessTokenForm)
if form.ClientID == "" { // if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header
if form.ClientID == "" || form.ClientSecret == "" {
authHeader := ctx.Req.Header.Get("Authorization") authHeader := ctx.Req.Header.Get("Authorization")
authContent := strings.SplitN(authHeader, " ", 2) authContent := strings.SplitN(authHeader, " ", 2)
if len(authContent) == 2 && authContent[0] == "Basic" { if len(authContent) == 2 && authContent[0] == "Basic" {
@ -608,7 +609,21 @@ func AccessTokenOAuth(ctx *context.Context) {
}) })
return return
} }
if form.ClientID != "" && form.ClientID != pair[0] {
handleAccessTokenError(ctx, AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "client_id in request body inconsistent with Authorization header",
})
return
}
form.ClientID = pair[0] form.ClientID = pair[0]
if form.ClientSecret != "" && form.ClientSecret != pair[1] {
handleAccessTokenError(ctx, AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "client_secret in request body inconsistent with Authorization header",
})
return
}
form.ClientSecret = pair[1] form.ClientSecret = pair[1]
} }
} }
@ -686,9 +701,13 @@ func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, s
return return
} }
if !app.ValidateClientSecret([]byte(form.ClientSecret)) { if !app.ValidateClientSecret([]byte(form.ClientSecret)) {
errorDescription := "invalid client secret"
if form.ClientSecret == "" {
errorDescription = "invalid empty client secret"
}
handleAccessTokenError(ctx, AccessTokenError{ handleAccessTokenError(ctx, AccessTokenError{
ErrorCode: AccessTokenErrorCodeUnauthorizedClient, ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
ErrorDescription: "client is not authorized", ErrorDescription: errorDescription,
}) })
return return
} }