Auth: Add key_id config param to auth.jwt (#72711)

* Specify keyID for public key provided in PEM format for JWT Auth

* Update docs

* Update sample.ini
This commit is contained in:
Misi 2023-08-03 09:13:23 +02:00 committed by GitHub
parent b4c55765fe
commit bba11d04cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 36 deletions

View File

@ -794,6 +794,7 @@ jwk_set_file =
cache_ttl = 60m cache_ttl = 60m
expect_claims = {} expect_claims = {}
key_file = key_file =
key_id =
role_attribute_path = role_attribute_path =
role_attribute_strict = false role_attribute_strict = false
auto_sign_up = false auto_sign_up = false

View File

@ -746,6 +746,8 @@
;cache_ttl = 60m ;cache_ttl = 60m
;expect_claims = {"aud": ["foo", "bar"]} ;expect_claims = {"aud": ["foo", "bar"]}
;key_file = /path/to/key/file ;key_file = /path/to/key/file
# Use in conjunction with key_file in case the JWT token's header specifies a key ID in "kid" field
;key_id = some-key-id
;role_attribute_path = ;role_attribute_path =
;role_attribute_strict = false ;role_attribute_strict = false
;auto_sign_up = false ;auto_sign_up = false

View File

@ -148,6 +148,12 @@ PEM-encoded key file in PKIX, PKCS #1, PKCS #8 or SEC 1 format.
key_file = /path/to/key.pem key_file = /path/to/key.pem
``` ```
If the JWT token's header specifies a `kid` (Key ID), then the Key ID must be set using the `key_id` configuration option.
```ini
key_id = my-key-id
```
## Validate claims ## Validate claims
By default, only `"exp"`, `"nbf"` and `"iat"` claims are validated. By default, only `"exp"`, `"nbf"` and `"iat"` claims are validated.

View File

@ -46,7 +46,7 @@ func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) {
scenario(t, "verifies a token", func(t *testing.T, sc scenarioContext) { scenario(t, "verifies a token", func(t *testing.T, sc scenarioContext) {
token := sign(t, key, jwt.Claims{ token := sign(t, key, jwt.Claims{
Subject: subject, Subject: subject,
}) }, nil)
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token) verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, verifiedClaims["sub"], subject) assert.Equal(t, verifiedClaims["sub"], subject)
@ -55,10 +55,23 @@ func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) {
scenario(t, "rejects a token signed by unknown key", func(t *testing.T, sc scenarioContext) { scenario(t, "rejects a token signed by unknown key", func(t *testing.T, sc scenarioContext) {
token := sign(t, unknownKey, jwt.Claims{ token := sign(t, unknownKey, jwt.Claims{
Subject: subject, Subject: subject,
}) }, nil)
_, err := sc.authJWTSvc.Verify(sc.ctx, token) _, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.Error(t, err) require.Error(t, err)
}, configurePKIXPublicKeyFile) }, configurePKIXPublicKeyFile)
publicKeyID := "some-key-id"
scenario(t, "verifies a token with a specified kid", func(t *testing.T, sc scenarioContext) {
token := sign(t, key, jwt.Claims{
Subject: subject,
}, (&jose.SignerOptions{}).WithHeader("kid", publicKeyID))
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.NoError(t, err)
assert.Equal(t, verifiedClaims["sub"], subject)
}, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) {
t.Helper()
cfg.JWTAuthKeyID = publicKeyID
})
} }
func TestVerifyUsingJWKSetFile(t *testing.T) { func TestVerifyUsingJWKSetFile(t *testing.T) {
@ -80,21 +93,21 @@ func TestVerifyUsingJWKSetFile(t *testing.T) {
} }
scenario(t, "verifies a token signed with a key from the set", func(t *testing.T, sc scenarioContext) { scenario(t, "verifies a token signed with a key from the set", func(t *testing.T, sc scenarioContext) {
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}) token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token) verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, verifiedClaims["sub"], subject) assert.Equal(t, verifiedClaims["sub"], subject)
}, configure) }, configure)
scenario(t, "verifies a token signed with another key from the set", func(t *testing.T, sc scenarioContext) { scenario(t, "verifies a token signed with another key from the set", func(t *testing.T, sc scenarioContext) {
token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}) token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}, nil)
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token) verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, verifiedClaims["sub"], subject) assert.Equal(t, verifiedClaims["sub"], subject)
}, configure) }, configure)
scenario(t, "rejects a token signed with a key not from the set", func(t *testing.T, sc scenarioContext) { scenario(t, "rejects a token signed with a key not from the set", func(t *testing.T, sc scenarioContext) {
token := sign(t, jwKeys[2], jwt.Claims{Subject: subject}) token := sign(t, jwKeys[2], jwt.Claims{Subject: subject}, nil)
_, err := sc.authJWTSvc.Verify(sc.ctx, token) _, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.Error(t, err) require.Error(t, err)
}, configure) }, configure)
@ -126,21 +139,21 @@ func TestVerifyUsingJWKSetURL(t *testing.T) {
}) })
jwkHTTPScenario(t, "verifies a token signed with a key from the set", func(t *testing.T, sc scenarioContext) { jwkHTTPScenario(t, "verifies a token signed with a key from the set", func(t *testing.T, sc scenarioContext) {
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}) token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token) verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, verifiedClaims["sub"], subject) assert.Equal(t, verifiedClaims["sub"], subject)
}) })
jwkHTTPScenario(t, "verifies a token signed with another key from the set", func(t *testing.T, sc scenarioContext) { jwkHTTPScenario(t, "verifies a token signed with another key from the set", func(t *testing.T, sc scenarioContext) {
token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}) token := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}, nil)
verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token) verifiedClaims, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, verifiedClaims["sub"], subject) assert.Equal(t, verifiedClaims["sub"], subject)
}) })
jwkHTTPScenario(t, "rejects a token signed with a key not from the set", func(t *testing.T, sc scenarioContext) { jwkHTTPScenario(t, "rejects a token signed with a key not from the set", func(t *testing.T, sc scenarioContext) {
token := sign(t, jwKeys[2], jwt.Claims{Subject: subject}) token := sign(t, jwKeys[2], jwt.Claims{Subject: subject}, nil)
_, err := sc.authJWTSvc.Verify(sc.ctx, token) _, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.Error(t, err) require.Error(t, err)
}) })
@ -149,7 +162,7 @@ func TestVerifyUsingJWKSetURL(t *testing.T) {
func TestCachingJWKHTTPResponse(t *testing.T) { func TestCachingJWKHTTPResponse(t *testing.T) {
jwkCachingScenario(t, "caches the jwk response", func(t *testing.T, sc cachingScenarioContext) { jwkCachingScenario(t, "caches the jwk response", func(t *testing.T, sc cachingScenarioContext) {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}) token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
_, err := sc.authJWTSvc.Verify(sc.ctx, token) _, err := sc.authJWTSvc.Verify(sc.ctx, token)
require.NoError(t, err, "verify call %d", i+1) require.NoError(t, err, "verify call %d", i+1)
} }
@ -160,8 +173,8 @@ func TestCachingJWKHTTPResponse(t *testing.T) {
jwkCachingScenario(t, "respects TTL setting (while cached)", func(t *testing.T, sc cachingScenarioContext) { jwkCachingScenario(t, "respects TTL setting (while cached)", func(t *testing.T, sc cachingScenarioContext) {
var err error var err error
token0 := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}) token0 := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
token1 := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}) token1 := sign(t, &jwKeys[1], jwt.Claims{Subject: subject}, nil)
_, err = sc.authJWTSvc.Verify(sc.ctx, token0) _, err = sc.authJWTSvc.Verify(sc.ctx, token0)
require.NoError(t, err) require.NoError(t, err)
@ -176,7 +189,7 @@ func TestCachingJWKHTTPResponse(t *testing.T) {
jwkCachingScenario(t, "does not cache the response when TTL is zero", func(t *testing.T, sc cachingScenarioContext) { jwkCachingScenario(t, "does not cache the response when TTL is zero", func(t *testing.T, sc cachingScenarioContext) {
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
_, err := sc.authJWTSvc.Verify(sc.ctx, sign(t, &jwKeys[i], jwt.Claims{Subject: subject})) _, err := sc.authJWTSvc.Verify(sc.ctx, sign(t, &jwKeys[i], jwt.Claims{Subject: subject}, nil))
require.NoError(t, err, "verify call %d", i+1) require.NoError(t, err, "verify call %d", i+1)
} }
@ -198,8 +211,8 @@ func TestClaimValidation(t *testing.T) {
key := rsaKeys[0] key := rsaKeys[0]
scenario(t, "validates iss field for equality", func(t *testing.T, sc scenarioContext) { scenario(t, "validates iss field for equality", func(t *testing.T, sc scenarioContext) {
tokenValid := sign(t, key, jwt.Claims{Issuer: "http://foo"}) tokenValid := sign(t, key, jwt.Claims{Issuer: "http://foo"}, nil)
tokenInvalid := sign(t, key, jwt.Claims{Issuer: "http://bar"}) tokenInvalid := sign(t, key, jwt.Claims{Issuer: "http://bar"}, nil)
_, err := sc.authJWTSvc.Verify(sc.ctx, tokenValid) _, err := sc.authJWTSvc.Verify(sc.ctx, tokenValid)
require.NoError(t, err) require.NoError(t, err)
@ -213,8 +226,8 @@ func TestClaimValidation(t *testing.T) {
scenario(t, "validates sub field for equality", func(t *testing.T, sc scenarioContext) { scenario(t, "validates sub field for equality", func(t *testing.T, sc scenarioContext) {
var err error var err error
tokenValid := sign(t, key, jwt.Claims{Subject: "foo"}) tokenValid := sign(t, key, jwt.Claims{Subject: "foo"}, nil)
tokenInvalid := sign(t, key, jwt.Claims{Subject: "bar"}) tokenInvalid := sign(t, key, jwt.Claims{Subject: "bar"}, nil)
_, err = sc.authJWTSvc.Verify(sc.ctx, tokenValid) _, err = sc.authJWTSvc.Verify(sc.ctx, tokenValid)
require.NoError(t, err) require.NoError(t, err)
@ -228,19 +241,19 @@ func TestClaimValidation(t *testing.T) {
scenario(t, "validates aud field for inclusion", func(t *testing.T, sc scenarioContext) { scenario(t, "validates aud field for inclusion", func(t *testing.T, sc scenarioContext) {
var err error var err error
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "foo"}})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "foo"}}, nil))
require.NoError(t, err) require.NoError(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo", "bar", "baz"}})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo", "bar", "baz"}}, nil))
require.NoError(t, err) require.NoError(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo"}})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"foo"}}, nil))
require.Error(t, err) require.Error(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "baz"}})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"bar", "baz"}}, nil))
require.Error(t, err) require.Error(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"baz"}})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Audience: []string{"baz"}}, nil))
require.Error(t, err) require.Error(t, err)
}, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) { }, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) {
cfg.JWTAuthExpectClaims = `{"aud": ["foo", "bar"]}` cfg.JWTAuthExpectClaims = `{"aud": ["foo", "bar"]}`
@ -249,19 +262,19 @@ func TestClaimValidation(t *testing.T) {
scenario(t, "validates non-registered (custom) claims for equality", func(t *testing.T, sc scenarioContext) { scenario(t, "validates non-registered (custom) claims for equality", func(t *testing.T, sc scenarioContext) {
var err error var err error
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 123})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 123}, nil))
require.NoError(t, err) require.NoError(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "bar", "my-number": 123})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "bar", "my-number": 123}, nil))
require.Error(t, err) require.Error(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 100})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo", "my-number": 100}, nil))
require.Error(t, err) require.Error(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo"})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-str": "foo"}, nil))
require.Error(t, err) require.Error(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-number": 123})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, map[string]interface{}{"my-number": 123}, nil))
require.Error(t, err) require.Error(t, err)
}, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) { }, configurePKIXPublicKeyFile, func(t *testing.T, cfg *setting.Cfg) {
cfg.JWTAuthExpectClaims = `{"my-str": "foo", "my-number": 123}` cfg.JWTAuthExpectClaims = `{"my-str": "foo", "my-number": 123}`
@ -270,30 +283,30 @@ func TestClaimValidation(t *testing.T) {
scenario(t, "validates exp claim of the token", func(t *testing.T, sc scenarioContext) { scenario(t, "validates exp claim of the token", func(t *testing.T, sc scenarioContext) {
var err error var err error
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour))})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour))}, nil))
require.NoError(t, err) require.NoError(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(-time.Hour))})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{Expiry: jwt.NewNumericDate(time.Now().Add(-time.Hour))}, nil))
require.Error(t, err) require.Error(t, err)
}, configurePKIXPublicKeyFile) }, configurePKIXPublicKeyFile)
scenario(t, "validates nbf claim of the token", func(t *testing.T, sc scenarioContext) { scenario(t, "validates nbf claim of the token", func(t *testing.T, sc scenarioContext) {
var err error var err error
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Hour))})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Hour))}, nil))
require.NoError(t, err) require.NoError(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Hour))})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Hour))}, nil))
require.Error(t, err) require.Error(t, err)
}, configurePKIXPublicKeyFile) }, configurePKIXPublicKeyFile)
scenario(t, "validates iat claim of the token", func(t *testing.T, sc scenarioContext) { scenario(t, "validates iat claim of the token", func(t *testing.T, sc scenarioContext) {
var err error var err error
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour))})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour))}, nil))
require.NoError(t, err) require.NoError(t, err)
_, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(time.Hour))})) _, err = sc.authJWTSvc.Verify(sc.ctx, sign(t, key, jwt.Claims{IssuedAt: jwt.NewNumericDate(time.Now().Add(time.Hour))}, nil))
require.Error(t, err) require.Error(t, err)
}, configurePKIXPublicKeyFile) }, configurePKIXPublicKeyFile)
} }
@ -360,7 +373,7 @@ func TestBase64Paddings(t *testing.T) {
scenario(t, "verifies a token with base64 padding (non compliant rfc7515#section-2 but accepted)", func(t *testing.T, sc scenarioContext) { scenario(t, "verifies a token with base64 padding (non compliant rfc7515#section-2 but accepted)", func(t *testing.T, sc scenarioContext) {
token := sign(t, key, jwt.Claims{ token := sign(t, key, jwt.Claims{
Subject: subject, Subject: subject,
}) }, nil)
var tokenParts []string var tokenParts []string
for i, part := range strings.Split(token, ".") { for i, part := range strings.Split(token, ".") {
// Create parts with different padding numbers to test multiple cases. // Create parts with different padding numbers to test multiple cases.

View File

@ -125,7 +125,7 @@ func (s *AuthService) initKeySet() error {
s.keySet = &keySetJWKS{ s.keySet = &keySetJWKS{
jose.JSONWebKeySet{ jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{{Key: key}}, Keys: []jose.JSONWebKey{{Key: key, KeyID: s.Cfg.JWTAuthKeyID}},
}, },
} }
} else if keyFilePath := s.Cfg.JWTAuthJWKSetFile; keyFilePath != "" { } else if keyFilePath := s.Cfg.JWTAuthJWKSetFile; keyFilePath != "" {

View File

@ -10,10 +10,13 @@ import (
type noneSigner struct{} type noneSigner struct{}
func sign(t *testing.T, key interface{}, claims interface{}) string { func sign(t *testing.T, key interface{}, claims interface{}, opts *jose.SignerOptions) string {
t.Helper() t.Helper()
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.PS512, Key: key}, (&jose.SignerOptions{}).WithType("JWT")) if opts == nil {
opts = &jose.SignerOptions{}
}
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.PS512, Key: key}, (opts).WithType("JWT"))
require.NoError(t, err) require.NoError(t, err)
token, err := jwt.Signed(sig).Claims(claims).CompactSerialize() token, err := jwt.Signed(sig).Claims(claims).CompactSerialize()
require.NoError(t, err) require.NoError(t, err)

View File

@ -318,6 +318,7 @@ type Cfg struct {
JWTAuthJWKSetURL string JWTAuthJWKSetURL string
JWTAuthCacheTTL time.Duration JWTAuthCacheTTL time.Duration
JWTAuthKeyFile string JWTAuthKeyFile string
JWTAuthKeyID string
JWTAuthJWKSetFile string JWTAuthJWKSetFile string
JWTAuthAutoSignUp bool JWTAuthAutoSignUp bool
JWTAuthRoleAttributePath string JWTAuthRoleAttributePath string
@ -1597,6 +1598,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
cfg.JWTAuthJWKSetURL = valueAsString(authJWT, "jwk_set_url", "") cfg.JWTAuthJWKSetURL = valueAsString(authJWT, "jwk_set_url", "")
cfg.JWTAuthCacheTTL = authJWT.Key("cache_ttl").MustDuration(time.Minute * 60) cfg.JWTAuthCacheTTL = authJWT.Key("cache_ttl").MustDuration(time.Minute * 60)
cfg.JWTAuthKeyFile = valueAsString(authJWT, "key_file", "") cfg.JWTAuthKeyFile = valueAsString(authJWT, "key_file", "")
cfg.JWTAuthKeyID = authJWT.Key("key_id").MustString("")
cfg.JWTAuthJWKSetFile = valueAsString(authJWT, "jwk_set_file", "") cfg.JWTAuthJWKSetFile = valueAsString(authJWT, "jwk_set_file", "")
cfg.JWTAuthAutoSignUp = authJWT.Key("auto_sign_up").MustBool(false) cfg.JWTAuthAutoSignUp = authJWT.Key("auto_sign_up").MustBool(false)
cfg.JWTAuthRoleAttributePath = valueAsString(authJWT, "role_attribute_path", "") cfg.JWTAuthRoleAttributePath = valueAsString(authJWT, "role_attribute_path", "")