mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-04-08 14:57:58 +00:00
feat(oidc): support access token in body for user info post (#769)
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
|
|
||||||
"github.com/steveiliop56/tinyauth/internal/service"
|
"github.com/steveiliop56/tinyauth/internal/service"
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||||
@@ -376,14 +377,15 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var token string
|
||||||
|
|
||||||
authorization := c.GetHeader("Authorization")
|
authorization := c.GetHeader("Authorization")
|
||||||
|
if authorization != "" {
|
||||||
tokenType, token, ok := strings.Cut(authorization, " ")
|
tokenType, bearerToken, ok := strings.Cut(authorization, " ")
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
tlog.App.Warn().Msg("OIDC userinfo accessed without authorization header")
|
tlog.App.Warn().Msg("OIDC userinfo accessed with malformed authorization header")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"error": "invalid_grant",
|
"error": "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -391,7 +393,32 @@ func (controller *OIDCController) Userinfo(c *gin.Context) {
|
|||||||
if strings.ToLower(tokenType) != "bearer" {
|
if strings.ToLower(tokenType) != "bearer" {
|
||||||
tlog.App.Warn().Msg("OIDC userinfo accessed with invalid token type")
|
tlog.App.Warn().Msg("OIDC userinfo accessed with invalid token type")
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"error": "invalid_grant",
|
"error": "invalid_request",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token = bearerToken
|
||||||
|
} else if c.Request.Method == http.MethodPost {
|
||||||
|
if c.ContentType() != "application/x-www-form-urlencoded" {
|
||||||
|
tlog.App.Warn().Msg("OIDC userinfo POST accessed with invalid content type")
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": "invalid_request",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token = c.PostForm("access_token")
|
||||||
|
if token == "" {
|
||||||
|
tlog.App.Warn().Msg("OIDC userinfo POST accessed without access_token in body")
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"error": "invalid_request",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tlog.App.Warn().Msg("OIDC userinfo accessed without authorization header")
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"error": "invalid_request",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -435,6 +435,128 @@ func TestOIDCController(t *testing.T) {
|
|||||||
assert.False(t, ok, "Did not expect email claim in userinfo response")
|
assert.False(t, ok, "Did not expect email claim in userinfo response")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Ensure userinfo forbids access with no authorization header",
|
||||||
|
middlewares: []gin.HandlerFunc{},
|
||||||
|
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||||
|
req := httptest.NewRequest("GET", "/api/oidc/userinfo", nil)
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, 401, recorder.Code)
|
||||||
|
|
||||||
|
var res map[string]any
|
||||||
|
err := json.Unmarshal(recorder.Body.Bytes(), &res)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "invalid_request", res["error"])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Ensure userinfo forbids access with malformed authorization header",
|
||||||
|
middlewares: []gin.HandlerFunc{},
|
||||||
|
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||||
|
req := httptest.NewRequest("GET", "/api/oidc/userinfo", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, 401, recorder.Code)
|
||||||
|
|
||||||
|
var res map[string]any
|
||||||
|
err := json.Unmarshal(recorder.Body.Bytes(), &res)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "invalid_request", res["error"])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Ensure userinfo forbids access with invalid token type",
|
||||||
|
middlewares: []gin.HandlerFunc{},
|
||||||
|
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||||
|
req := httptest.NewRequest("GET", "/api/oidc/userinfo", nil)
|
||||||
|
req.Header.Set("Authorization", "Basic some-token")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, 401, recorder.Code)
|
||||||
|
|
||||||
|
var res map[string]any
|
||||||
|
err := json.Unmarshal(recorder.Body.Bytes(), &res)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "invalid_request", res["error"])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Ensure userinfo forbids access with empty bearer token",
|
||||||
|
middlewares: []gin.HandlerFunc{},
|
||||||
|
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||||
|
req := httptest.NewRequest("GET", "/api/oidc/userinfo", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer ")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, 401, recorder.Code)
|
||||||
|
|
||||||
|
var res map[string]any
|
||||||
|
err := json.Unmarshal(recorder.Body.Bytes(), &res)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "invalid_grant", res["error"])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Ensure userinfo POST rejects missing access token in body",
|
||||||
|
middlewares: []gin.HandlerFunc{},
|
||||||
|
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||||
|
req := httptest.NewRequest("POST", "/api/oidc/userinfo", strings.NewReader(""))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, 401, recorder.Code)
|
||||||
|
|
||||||
|
var res map[string]any
|
||||||
|
err := json.Unmarshal(recorder.Body.Bytes(), &res)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "invalid_request", res["error"])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Ensure userinfo POST rejects wrong content type",
|
||||||
|
middlewares: []gin.HandlerFunc{},
|
||||||
|
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||||
|
req := httptest.NewRequest("POST", "/api/oidc/userinfo", strings.NewReader(`{"access_token":"some-token"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, 400, recorder.Code)
|
||||||
|
|
||||||
|
var res map[string]any
|
||||||
|
err := json.Unmarshal(recorder.Body.Bytes(), &res)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "invalid_request", res["error"])
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Ensure userinfo accepts access token via POST body",
|
||||||
|
middlewares: []gin.HandlerFunc{
|
||||||
|
simpleCtx,
|
||||||
|
},
|
||||||
|
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||||
|
tokenTest, found := getTestByDescription("Ensure we can get a token with a valid request")
|
||||||
|
assert.True(t, found, "Token test not found")
|
||||||
|
tokenRecorder := httptest.NewRecorder()
|
||||||
|
tokenTest(t, router, tokenRecorder)
|
||||||
|
|
||||||
|
var tokenRes map[string]any
|
||||||
|
err := json.Unmarshal(tokenRecorder.Body.Bytes(), &tokenRes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
accessToken := tokenRes["access_token"].(string)
|
||||||
|
assert.NotEmpty(t, accessToken)
|
||||||
|
|
||||||
|
body := url.Values{}
|
||||||
|
body.Set("access_token", accessToken)
|
||||||
|
req := httptest.NewRequest("POST", "/api/oidc/userinfo", strings.NewReader(body.Encode()))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, 200, recorder.Code)
|
||||||
|
|
||||||
|
var userInfoRes map[string]any
|
||||||
|
err = json.Unmarshal(recorder.Body.Bytes(), &userInfoRes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, ok := userInfoRes["sub"]
|
||||||
|
assert.True(t, ok, "Expected sub claim in userinfo response")
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Ensure plain PKCE succeeds",
|
description: "Ensure plain PKCE succeeds",
|
||||||
middlewares: []gin.HandlerFunc{
|
middlewares: []gin.HandlerFunc{
|
||||||
|
|||||||
Reference in New Issue
Block a user