JWT: Find login and email claims with JMESPATH (#85305)

* add function to static function to static service

* find email and login claims with jmespath

* rename configuration files

* Replace JWTClaims struct for map

* check for subclaims error
This commit is contained in:
linoman
2024-03-28 10:25:26 -06:00
committed by GitHub
parent 18f3c7188b
commit e4250a72db
9 changed files with 122 additions and 21 deletions

View File

@@ -78,10 +78,23 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
if key := s.cfg.JWTAuth.UsernameClaim; key != "" {
id.Login, _ = claims[key].(string)
id.ClientParams.LookUpParams.Login = &id.Login
} else if key := s.cfg.JWTAuth.UsernameAttributePath; key != "" {
id.Login, err = util.SearchJSONForStringAttr(s.cfg.JWTAuth.UsernameAttributePath, claims)
if err != nil {
return nil, err
}
id.ClientParams.LookUpParams.Login = &id.Login
}
if key := s.cfg.JWTAuth.EmailClaim; key != "" {
id.Email, _ = claims[key].(string)
id.ClientParams.LookUpParams.Email = &id.Email
} else if key := s.cfg.JWTAuth.EmailAttributePath; key != "" {
id.Email, err = util.SearchJSONForStringAttr(s.cfg.JWTAuth.EmailAttributePath, claims)
if err != nil {
return nil, err
}
id.ClientParams.LookUpParams.Email = &id.Email
}
if name, _ := claims["name"].(string); name != "" {

View File

@@ -30,7 +30,7 @@ func TestAuthenticateJWT(t *testing.T) {
testCases := []struct {
name string
wantID *authn.Identity
verifyProvider func(context.Context, string) (jwt.JWTClaims, error)
verifyProvider func(context.Context, string) (map[string]any, error)
cfg *setting.Cfg
}{
{
@@ -63,8 +63,8 @@ func TestAuthenticateJWT(t *testing.T) {
},
},
},
verifyProvider: func(context.Context, string) (jwt.JWTClaims, error) {
return jwt.JWTClaims{
verifyProvider: func(context.Context, string) (map[string]any, error) {
return map[string]any{
"sub": "1234567890",
"email": "eai.doe@cor.po",
"preferred_username": "eai-doe",
@@ -117,8 +117,8 @@ func TestAuthenticateJWT(t *testing.T) {
},
},
},
verifyProvider: func(context.Context, string) (jwt.JWTClaims, error) {
return jwt.JWTClaims{
verifyProvider: func(context.Context, string) (map[string]any, error) {
return map[string]any{
"sub": "1234567890",
"email": "eai.doe@cor.po",
"preferred_username": "eai-doe",
@@ -171,8 +171,8 @@ func TestAuthenticateJWT(t *testing.T) {
func TestJWTClaimConfig(t *testing.T) {
t.Parallel()
jwtService := &jwt.FakeJWTService{
VerifyProvider: func(context.Context, string) (jwt.JWTClaims, error) {
return jwt.JWTClaims{
VerifyProvider: func(context.Context, string) (map[string]any, error) {
return map[string]any{
"sub": "1234567890",
"email": "eai.doe@cor.po",
"preferred_username": "eai-doe",
@@ -399,8 +399,8 @@ func TestJWTTest(t *testing.T) {
func TestJWTStripParam(t *testing.T) {
t.Parallel()
jwtService := &jwt.FakeJWTService{
VerifyProvider: func(context.Context, string) (jwt.JWTClaims, error) {
return jwt.JWTClaims{
VerifyProvider: func(context.Context, string) (map[string]any, error) {
return map[string]any{
"sub": "1234567890",
"email": "eai.doe@cor.po",
"preferred_username": "eai-doe",
@@ -442,3 +442,61 @@ func TestJWTStripParam(t *testing.T) {
// auth_token should be removed from the query string
assert.Equal(t, "other_param=other_value", httpReq.URL.RawQuery)
}
func TestJWTSubClaimsConfig(t *testing.T) {
t.Parallel()
// #nosec G101 -- This is a dummy/test token
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXIiOiIxLjAiLCJpc3MiOiJodHRwczovL2F6dXJlZG9tYWlubmFtZS5iMmNsb2dpbi5jb20vNjIwYjI2MzQtYmI4OC00MzdiLTgwYWQtYWM0YTkwZGZkZTkxL3YyLjAvIiwic3ViIjoiOWI4OTg5MDgtMWFlYy00NDc1LTljNDgtNzg1MWQyNjVkZGIxIiwiYXVkIjoiYmEyNzM0NDktMmZiNS00YTRhLTlmODItYTA2MTRhM2MxODQ1IiwiZXhwIjoxNzExNTYwMDcxLCJub25jZSI6ImRlZmF1bHROb25jZSIsImlhdCI6MTcxMTU1NjQ3MSwiYXV0aF90aW1lIjoxNzExNTU2NDcxLCJuYW1lIjoibmFtZV9vZl90aGVfdXNlciIsImdpdmVuX25hbWUiOiJVc2VyTmFtZSIsImZhbWlseV9uYW1lIjoiVXNlclN1cm5hbWUiLCJlbWFpbHMiOlsibWFpbmVtYWlsK2V4dHJhZW1haWwwNUBnbWFpbC5jb20iLCJtYWluZW1haWwrZXh0cmFlbWFpbDA0QGdtYWlsLmNvbSIsIm1haW5lbWFpbCtleHRyYWVtYWlsMDNAZ21haWwuY29tIiwibWFpbmVtYWlsK2V4dHJhZW1haWwwMkBnbWFpbC5jb20iLCJtYWluZW1haWwrZXh0cmFlbWFpbDAxQGdtYWlsLmNvbSIsIm1haW5lbWFpbEBnbWFpbC5jb20iXSwidGZwIjoiQjJDXzFfdXNlcmZsb3ciLCJuYmYiOjE3MTE1NTY0NzF9.qpN3upxUB5CTJ7kmYPHFuhlwG95vdQqJaDDC_8KJFZ8"
jwtHeaderName := "X-Forwarded-User"
response := map[string]any{
"ver": "1.0",
"iss": "https://azuredomainname.b2clogin.com/620b2634-bb88-437b-80ad-ac4a90dfde91/v2.0/",
"sub": "9b898908-1aec-4475-9c48-7851d265ddb1",
"aud": "ba273449-2fb5-4a4a-9f82-a0614a3c1845",
"exp": 1711560071,
"nonce": "defaultNonce",
"iat": 1711556471,
"auth_time": 1711556471,
"name": "name_of_the_user",
"given_name": "UserName",
"family_name": "UserSurname",
"emails": []string{
"mainemail+extraemail04@gmail.com",
"mainemail+extraemail03@gmail.com",
"mainemail+extraemail02@gmail.com",
"mainemail+extraemail01@gmail.com",
"mainemail@gmail.com",
},
"tfp": "B2C_1_userflow",
"nbf": 1711556471,
}
cfg := &setting.Cfg{
JWTAuth: setting.AuthJWTSettings{
HeaderName: jwtHeaderName,
EmailAttributePath: "emails[2]",
UsernameAttributePath: "name",
},
}
httpReq := &http.Request{
URL: &url.URL{RawQuery: "auth_token=" + token},
Header: map[string][]string{
jwtHeaderName: {token}},
}
jwtService := &jwt.FakeJWTService{
VerifyProvider: func(context.Context, string) (map[string]any, error) {
return response, nil
},
}
jwtClient := ProvideJWT(jwtService, cfg)
identity, err := jwtClient.Authenticate(context.Background(), &authn.Request{
OrgID: 1,
HTTPRequest: httpReq,
Resp: nil,
})
require.NoError(t, err)
require.Equal(t, "mainemail+extraemail02@gmail.com", identity.Email)
require.Equal(t, "name_of_the_user", identity.Name)
fmt.Println("identity.Email", identity.Email)
}