1
0
mirror of https://github.com/pocket-id/pocket-id.git synced 2026-02-04 09:51:46 +00:00

fix: decode URL-encoded client ID and secret in Basic auth (#1263)

This commit is contained in:
Yegor Pomortsev
2026-01-24 12:52:17 -08:00
committed by GitHub
parent 1de231f1ff
commit 6eebecd85a
3 changed files with 84 additions and 3 deletions

View File

@@ -164,7 +164,7 @@ func (oc *OidcController) createTokensHandler(c *gin.Context) {
// Client id and secret can also be passed over the Authorization header // Client id and secret can also be passed over the Authorization header
if input.ClientID == "" && input.ClientSecret == "" { if input.ClientID == "" && input.ClientSecret == "" {
input.ClientID, input.ClientSecret, _ = c.Request.BasicAuth() input.ClientID, input.ClientSecret, _ = utils.OAuthClientBasicAuth(c.Request)
} }
tokens, err := oc.oidcService.CreateTokens(c.Request.Context(), input) tokens, err := oc.oidcService.CreateTokens(c.Request.Context(), input)
@@ -322,7 +322,7 @@ func (oc *OidcController) introspectTokenHandler(c *gin.Context) {
creds service.ClientAuthCredentials creds service.ClientAuthCredentials
ok bool ok bool
) )
creds.ClientID, creds.ClientSecret, ok = c.Request.BasicAuth() creds.ClientID, creds.ClientSecret, ok = utils.OAuthClientBasicAuth(c.Request)
if !ok { if !ok {
// If there's no basic auth, check if we have a bearer token // If there's no basic auth, check if we have a bearer token
bearer, ok := utils.BearerAuth(c.Request) bearer, ok := utils.BearerAuth(c.Request)
@@ -659,7 +659,7 @@ func (oc *OidcController) deviceAuthorizationHandler(c *gin.Context) {
// Client id and secret can also be passed over the Authorization header // Client id and secret can also be passed over the Authorization header
if input.ClientID == "" && input.ClientSecret == "" { if input.ClientID == "" && input.ClientSecret == "" {
input.ClientID, input.ClientSecret, _ = c.Request.BasicAuth() input.ClientID, input.ClientSecret, _ = utils.OAuthClientBasicAuth(c.Request)
} }
response, err := oc.oidcService.CreateDeviceAuthorization(c.Request.Context(), input) response, err := oc.oidcService.CreateDeviceAuthorization(c.Request.Context(), input)

View File

@@ -2,6 +2,7 @@ package utils
import ( import (
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -21,6 +22,27 @@ func BearerAuth(r *http.Request) (string, bool) {
return "", false return "", false
} }
// OAuthClientBasicAuth returns the OAuth client ID and secret provided in the request's
// Authorization header, if present. See RFC 6749, Section 2.3.
func OAuthClientBasicAuth(r *http.Request) (clientID, clientSecret string, ok bool) {
clientID, clientSecret, ok = r.BasicAuth()
if !ok {
return "", "", false
}
clientID, err := url.QueryUnescape(clientID)
if err != nil {
return "", "", false
}
clientSecret, err = url.QueryUnescape(clientSecret)
if err != nil {
return "", "", false
}
return clientID, clientSecret, true
}
// SetCacheControlHeader sets the Cache-Control header for the response. // SetCacheControlHeader sets the Cache-Control header for the response.
func SetCacheControlHeader(ctx *gin.Context, maxAge, staleWhileRevalidate time.Duration) { func SetCacheControlHeader(ctx *gin.Context, maxAge, staleWhileRevalidate time.Duration) {
_, ok := ctx.GetQuery("skipCache") _, ok := ctx.GetQuery("skipCache")

View File

@@ -63,3 +63,62 @@ func TestBearerAuth(t *testing.T) {
}) })
} }
} }
func TestOAuthClientBasicAuth(t *testing.T) {
tests := []struct {
name string
authHeader string
expectedClientID string
expectedClientSecret string
expectedOk bool
}{
{
name: "Valid client ID and secret in header (example from RFC 6749)",
authHeader: "Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3",
expectedClientID: "s6BhdRkqt3",
expectedClientSecret: "7Fjfp0ZBr1KtDRbnfVdmIw",
expectedOk: true,
},
{
name: "Valid client ID and secret in header (escaped values)",
authHeader: "Basic ZTUwOTcyYmQtNmUzMi00OTU3LWJhZmMtMzU0MTU3ZjI1NDViOislMjUlMjYlMkIlQzIlQTMlRTIlODIlQUM=",
expectedClientID: "e50972bd-6e32-4957-bafc-354157f2545b",
// This is the example string from RFC 6749, Appendix B.
expectedClientSecret: " %&+£€",
expectedOk: true,
},
{
name: "Empty auth header",
authHeader: "",
expectedClientID: "",
expectedClientSecret: "",
expectedOk: false,
},
{
name: "Basic prefix only",
authHeader: "Basic ",
expectedClientID: "",
expectedClientSecret: "",
expectedOk: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://example.com", nil)
require.NoError(t, err, "Failed to create request")
if tt.authHeader != "" {
req.Header.Set("Authorization", tt.authHeader)
}
clientId, clientSecret, ok := OAuthClientBasicAuth(req)
assert.Equal(t, tt.expectedOk, ok)
if tt.expectedOk {
assert.Equal(t, tt.expectedClientID, clientId)
assert.Equal(t, tt.expectedClientSecret, clientSecret)
}
})
}
}