Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(GraphQL): Allow Multiple JWKUrls for auth. (#7528) #7581

Merged
merged 1 commit into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 76 additions & 29 deletions graphql/authorization/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ var (
type AuthMeta struct {
VerificationKey string
JWKUrl string
jwkSet *jose.JSONWebKeySet
expiryTime time.Time
JWKUrls []string
jwkSet []*jose.JSONWebKeySet
expiryTime []time.Time
RSAPublicKey *rsa.PublicKey `json:"-"` // Ignoring this field
Header string
Namespace string
Expand All @@ -73,11 +74,17 @@ type AuthMeta struct {
func (a *AuthMeta) validate() error {
var fields string

// If JWKUrl is provided, we don't expect (VerificationKey, Algo),
// they are needed only if JWKUrl is not present there.
if a.JWKUrl != "" {
// If JWKUrl/JWKUrls is provided, we don't expect (VerificationKey, Algo),
// they are needed only if JWKUrl/JWKUrls is not present there.
if len(a.JWKUrls) != 0 || a.JWKUrl != "" {

// User cannot provide both JWKUrl and JWKUrls.
if len(a.JWKUrls) != 0 && a.JWKUrl != "" {
return fmt.Errorf("expecting either JWKUrl or JWKUrls, both were given")
}

if a.VerificationKey != "" || a.Algo != "" {
return fmt.Errorf("expecting either JWKUrl or (VerificationKey, Algo), both were given")
return fmt.Errorf("expecting either JWKUrl/JWKUrls or (VerificationKey, Algo), both were given")
}

// Audience should be a required field if JWKUrl is provided.
Expand All @@ -86,7 +93,7 @@ func (a *AuthMeta) validate() error {
}
} else {
if a.VerificationKey == "" {
fields = " `Verification key`/`JWKUrl`"
fields = " `Verification key`/`JWKUrl`/`JWKUrls`"
}

if a.Algo == "" {
Expand Down Expand Up @@ -125,6 +132,15 @@ func Parse(schema string) (*AuthMeta, error) {
return nil, algoErr
}

if meta.JWKUrl != "" {
meta.JWKUrls = append(meta.JWKUrls, meta.JWKUrl)
meta.JWKUrl = ""
}

if len(meta.JWKUrls) != 0 {
meta.expiryTime = make([]time.Time, len(meta.JWKUrls))
meta.jwkSet = make([]*jose.JSONWebKeySet, len(meta.JWKUrls))
}
return &meta, nil
}

Expand Down Expand Up @@ -329,13 +345,15 @@ func GetJwtToken(ctx context.Context) string {
return jwtToken[0]
}

func (a *AuthMeta) validateJWTCustomClaims(jwtStr string) (*CustomClaims, error) {
var token *jwt.Token
// validateThroughJWKUrl validates the JWT token against the given list of JWKUrls.
// It returns an error only if the token is not validated against even one of the
// JWKUrl.
func (a *AuthMeta) validateThroughJWKUrl(jwtStr string) (*jwt.Token, error) {
var err error
// Verification through JWKUrl
if a.JWKUrl != "" {
if a.isExpired() {
err = a.refreshJWK()
var token *jwt.Token
for i := 0; i < len(a.JWKUrls); i++ {
if a.isExpired(i) {
err = a.refreshJWK(i)
if err != nil {
return nil, errors.Wrap(err, "while refreshing JWK from the URL")
}
Expand All @@ -348,12 +366,26 @@ func (a *AuthMeta) validateJWTCustomClaims(jwtStr string) (*CustomClaims, error)
return nil, errors.Errorf("kid not present in JWT")
}

signingKeys := a.jwkSet.Key(kid.(string))
signingKeys := a.jwkSet[i].Key(kid.(string))
if len(signingKeys) == 0 {
return nil, errors.Errorf("Invalid kid")
}
return signingKeys[0].Key, nil
}, jwt.WithoutAudienceValidation())

if err == nil {
return token, nil
}
}
return nil, err
}

func (a *AuthMeta) validateJWTCustomClaims(jwtStr string) (*CustomClaims, error) {
var token *jwt.Token
var err error
// Verification through JWKUrl
if len(a.JWKUrls) != 0 {
token, err = a.validateThroughJWKUrl(jwtStr)
} else {
if a.Algo == "" {
return nil, fmt.Errorf(
Expand Down Expand Up @@ -397,14 +429,29 @@ func (a *AuthMeta) validateJWTCustomClaims(jwtStr string) (*CustomClaims, error)
return claims, nil
}

// FetchJWKs fetches the JSON Web Key set from a JWKUrl. It acquires a Lock over a as some of the
// properties of AuthMeta are modified in the process.
// FetchJWKs fetches the JSON Web Key sets for the JWKUrls. It returns an error if
// the fetching of key is failed even for one of the JWKUrl.
func (a *AuthMeta) FetchJWKs() error {
if a.JWKUrl == "" {
if len(a.JWKUrls) == 0 {
return errors.Errorf("No JWKUrl supplied")
}

req, err := http.NewRequest("GET", a.JWKUrl, nil)
for i := 0; i < len(a.JWKUrls); i++ {
err := a.FetchJWK(i)
if err != nil {
return err
}
}
return nil
}

// FetchJWK fetches the JSON web Key set for the JWKUrl at a given index.
func (a *AuthMeta) FetchJWK(i int) error {
if len(a.JWKUrls) <= i {
return errors.Errorf("not enough JWKUrls")
}

req, err := http.NewRequest("GET", a.JWKUrls[i], nil)
if err != nil {
return err
}
Expand All @@ -430,9 +477,9 @@ func (a *AuthMeta) FetchJWKs() error {
return err
}

a.jwkSet = &jose.JSONWebKeySet{Keys: make([]jose.JSONWebKey, len(jwkArray.JWKs))}
for i, jwk := range jwkArray.JWKs {
err = a.jwkSet.Keys[i].UnmarshalJSON(jwk)
a.jwkSet[i] = &jose.JSONWebKeySet{Keys: make([]jose.JSONWebKey, len(jwkArray.JWKs))}
for k, jwk := range jwkArray.JWKs {
err = a.jwkSet[i].Keys[k].UnmarshalJSON(jwk)
if err != nil {
return err
}
Expand All @@ -447,18 +494,18 @@ func (a *AuthMeta) FetchJWKs() error {
}

if maxAge == 0 {
a.expiryTime = time.Time{}
a.expiryTime[i] = time.Time{}
} else {
a.expiryTime = time.Now().Add(time.Duration(maxAge) * time.Second)
a.expiryTime[i] = time.Now().Add(time.Duration(maxAge) * time.Second)
}

return nil
}

func (a *AuthMeta) refreshJWK() error {
func (a *AuthMeta) refreshJWK(i int) error {
var err error
for i := 0; i < 3; i++ {
err = a.FetchJWKs()
err = a.FetchJWK(i)
if err == nil {
return nil
}
Expand All @@ -471,18 +518,18 @@ func (a *AuthMeta) refreshJWK() error {
// if expiryTime is equal to 0 which means there
// is no expiry time of the JWKs, so it always
// returns false
func (a *AuthMeta) isExpired() bool {
if a.expiryTime.IsZero() {
func (a *AuthMeta) isExpired(i int) bool {
if a.expiryTime[i].IsZero() {
return false
}
return time.Now().After(a.expiryTime)
return time.Now().After(a.expiryTime[i])
}

// initSigningMethod takes the current Algo value, validates it's a supported SigningMethod, then sets the SigningMethod
// field.
func (a *AuthMeta) initSigningMethod() error {
// configurations using JWK URLs do not use signing methods.
if a.JWKUrl != "" {
if len(a.JWKUrls) != 0 || a.JWKUrl != "" {
return nil
}

Expand Down
63 changes: 57 additions & 6 deletions graphql/resolve/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ func TestInvalidAuthInfo(t *testing.T) {
authSchema, err := testutil.AppendJWKAndVerificationKey(sch)
require.NoError(t, err)
_, err = schema.NewHandler(string(authSchema), false)
require.Error(t, err, fmt.Errorf("Expecting either JWKUrl or (VerificationKey, Algo), both were given"))
require.Error(t, err, fmt.Errorf("Expecting either JWKUrl/JWKUrls or (VerificationKey, Algo), both were given"))
}

func TestMissingAudienceWithJWKUrl(t *testing.T) {
Expand All @@ -276,7 +276,6 @@ func TestMissingAudienceWithJWKUrl(t *testing.T) {
require.Error(t, err, fmt.Errorf("required field missing in Dgraph.Authorization: `Audience`"))
}

//Todo(Minhaj): Add a testcase for token without Expiry
func TestVerificationWithJWKUrl(t *testing.T) {
sch, err := ioutil.ReadFile("../e2e/auth/schema.graphql")
require.NoError(t, err, "Unable to read schema file")
Expand All @@ -293,21 +292,73 @@ func TestVerificationWithJWKUrl(t *testing.T) {
require.Equal(t, metainfo.Header, "X-Test-Auth")
require.Equal(t, metainfo.Namespace, "https://xyz.io/jwt/claims")
require.Equal(t, metainfo.VerificationKey, "")
require.Equal(t, metainfo.JWKUrl, "https://www.googleapis.com/service_accounts/v1/jwk/[email protected]")
require.Equal(t, metainfo.JWKUrl, "")
require.Equal(t, metainfo.JWKUrls, []string{"https://dev-hr2kugfp.us.auth0.com/.well-known/jwks.json"})

testCase := struct {
name string
token string
}{
name: `Expired Token`,
token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE2NzUwM2UwYWVjNTJkZGZiODk2NTIxYjkxN2ZiOGUyMGMxZjMzMDAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmlyLXByb2plY3QxLTI1OWU3IiwiYXVkIjoiZmlyLXByb2plY3QxLTI1OWU3IiwiYXV0aF90aW1lIjoxNjAxNDQ0NjM0LCJ1c2VyX2lkIjoiMTdHb3h2dU5CWlc5YTlKU3Z3WXhROFc0bjE2MyIsInN1YiI6IjE3R294dnVOQlpXOWE5SlN2d1l4UThXNG4xNjMiLCJpYXQiOjE2MDE0NDQ2MzQsImV4cCI6MTYwMTQ0ODIzNCwiZW1haWwiOiJtaW5oYWpAZGdyYXBoLmlvIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbIm1pbmhhakBkZ3JhcGguaW8iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.q5YmOzOUkZHNjlz53hgLNSVg-brIU9tLJ4jLC0_Xurl5wEbyZ6D_KQ9-UFqbl2HR6R1V5kpaf6eDFR3c83i1PpCbJ4LTjHAf_njQvL75ByERld23lZtKZyEeE6ujdFXL8ne4fI2qenD1Xeqx9AnXbLf7U_CvZpbX3l1wj7p0Lpn7qixi0AztuLSJMLkMfFpaiwyFZQivi4cqtnI25VIsK6a4KIpl1Sk0AHT-lv9PRadd_JDjWAIzD0SfhpZOskaeA9PljVMp-Y3Xscwg_Qc6u1MIBPg1jKO-ngjhWkgEWBoz5F836P7phT60LVBHhYuk-jRN6HSSNWQ3ineuN-jBkg",
name: `Valid Token`,
token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjJKdVZuRkc0Q2JBX0E1VVNkenlDMyJ9.eyJnaXZlbl9uYW1lIjoibWluaGFqIiwiZmFtaWx5X25hbWUiOiJzaGFrZWVsIiwibmlja25hbWUiOiJtc3JpaXRkIiwibmFtZSI6Im1pbmhhaiBzaGFrZWVsIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hLS9BT2gxNEdnYzVEZ2cyQThWZFNzWUNnc2RlR3lFMHM1d01Gdmd2X1htZDA4Q3B3PXM5Ni1jIiwibG9jYWxlIjoiZW4iLCJ1cGRhdGVkX2F0IjoiMjAyMS0wMy0wOVQxMDowOTozNi4yMDNaIiwiZW1haWwiOiJtc3JpaXRkQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL2Rldi1ocjJrdWdmcC51cy5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDM2NTgyNjIxNzU2NDczNzEwNjQiLCJhdWQiOiJIaGFYa1FWUkJuNWUwSzNEbU1wMnpiakk4aTF3Y3YyZSIsImlhdCI6MTYxNTI4NDU3NywiZXhwIjo1MjE1Mjg0NTc3LCJub25jZSI6IlVtUk9NbTV0WWtoR2NGVjVOWGRhVGtKV1UyWm5ZM0pKUzNSR1ZsWk1jRzFLZUVkMGQzWkdkVTFuYXc9PSJ9.rlVl0tGOCypIts0C52g1qyiNaFV3UnDafJETXTGbt-toWvtCyZsa-JySgwG0DD1rMYm-gdwyJcjJlgwVPQD3ZlkJqbFFNvY4cX5injiOljpVFOHKXdi7tehY9We_vv1KYYpvhGMsE4u7o8tz2wEctdLTXT7omEq7gSdHuDgpM-h-K2RLApU8oyu8YOIqQlrqGgJ7Q8jy-jxMlU7BoZVz38FokjmkSapAAVORsbdEqPgQjeDnjaDQ5bRhxZUMSeKvvpvtVlPaeM1NI4S0R3g0qUGvX6L6qsLZqIilSQUiUaOEo8bLNBFHOxhBbocF-R-x40nSYjdjrEz60A99mz5XAA",
}

md := metadata.New(map[string]string{"authorizationJwt": testCase.token})
ctx := metadata.NewIncomingContext(context.Background(), md)

_, err = metainfo.ExtractCustomClaims(ctx)
require.True(t, strings.Contains(err.Error(), "unable to parse jwt token:token is unverifiable: Keyfunc returned an error"))
require.Nil(t, err)
}

func TestVerificationWithMultipleJWKUrls(t *testing.T) {
sch, err := ioutil.ReadFile("../e2e/auth/schema.graphql")
require.NoError(t, err, "Unable to read schema file")

authSchema, err := testutil.AppendAuthInfoWithMultipleJWKUrls(sch)
require.NoError(t, err)

schema := test.LoadSchemaFromString(t, string(authSchema))
require.NotNil(t, schema.Meta().AuthMeta())

// Verify that authorization information is set correctly.
metainfo := schema.Meta().AuthMeta()
require.Equal(t, metainfo.Algo, "")
require.Equal(t, metainfo.Header, "X-Test-Auth")
require.Equal(t, metainfo.Namespace, "https://xyz.io/jwt/claims")
require.Equal(t, metainfo.VerificationKey, "")
require.Equal(t, metainfo.JWKUrl, "")
require.Equal(t, metainfo.JWKUrls, []string{"https://www.googleapis.com/service_accounts/v1/jwk/[email protected]", "https://dev-hr2kugfp.us.auth0.com/.well-known/jwks.json"})

testCases := []struct {
name string
token string
invalid bool
}{
{
name: `Expired Token`,
token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE2NzUwM2UwYWVjNTJkZGZiODk2NTIxYjkxN2ZiOGUyMGMxZjMzMDAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmlyLXByb2plY3QxLTI1OWU3IiwiYXVkIjoiZmlyLXByb2plY3QxLTI1OWU3IiwiYXV0aF90aW1lIjoxNjAxNDQ0NjM0LCJ1c2VyX2lkIjoiMTdHb3h2dU5CWlc5YTlKU3Z3WXhROFc0bjE2MyIsInN1YiI6IjE3R294dnVOQlpXOWE5SlN2d1l4UThXNG4xNjMiLCJpYXQiOjE2MDE0NDQ2MzQsImV4cCI6MTYwMTQ0ODIzNCwiZW1haWwiOiJtaW5oYWpAZGdyYXBoLmlvIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbIm1pbmhhakBkZ3JhcGguaW8iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.q5YmOzOUkZHNjlz53hgLNSVg-brIU9tLJ4jLC0_Xurl5wEbyZ6D_KQ9-UFqbl2HR6R1V5kpaf6eDFR3c83i1PpCbJ4LTjHAf_njQvL75ByERld23lZtKZyEeE6ujdFXL8ne4fI2qenD1Xeqx9AnXbLf7U_CvZpbX3l1wj7p0Lpn7qixi0AztuLSJMLkMfFpaiwyFZQivi4cqtnI25VIsK6a4KIpl1Sk0AHT-lv9PRadd_JDjWAIzD0SfhpZOskaeA9PljVMp-Y3Xscwg_Qc6u1MIBPg1jKO-ngjhWkgEWBoz5F836P7phT60LVBHhYuk-jRN6HSSNWQ3ineuN-jBkg",
invalid: true,
},
{
name: `Valid Token`,
token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjJKdVZuRkc0Q2JBX0E1VVNkenlDMyJ9.eyJnaXZlbl9uYW1lIjoibWluaGFqIiwiZmFtaWx5X25hbWUiOiJzaGFrZWVsIiwibmlja25hbWUiOiJtc3JpaXRkIiwibmFtZSI6Im1pbmhhaiBzaGFrZWVsIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hLS9BT2gxNEdnYzVEZ2cyQThWZFNzWUNnc2RlR3lFMHM1d01Gdmd2X1htZDA4Q3B3PXM5Ni1jIiwibG9jYWxlIjoiZW4iLCJ1cGRhdGVkX2F0IjoiMjAyMS0wMy0wOVQxMDowOTozNi4yMDNaIiwiZW1haWwiOiJtc3JpaXRkQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL2Rldi1ocjJrdWdmcC51cy5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDM2NTgyNjIxNzU2NDczNzEwNjQiLCJhdWQiOiJIaGFYa1FWUkJuNWUwSzNEbU1wMnpiakk4aTF3Y3YyZSIsImlhdCI6MTYxNTI4NDU3NywiZXhwIjo1MjE1Mjg0NTc3LCJub25jZSI6IlVtUk9NbTV0WWtoR2NGVjVOWGRhVGtKV1UyWm5ZM0pKUzNSR1ZsWk1jRzFLZUVkMGQzWkdkVTFuYXc9PSJ9.rlVl0tGOCypIts0C52g1qyiNaFV3UnDafJETXTGbt-toWvtCyZsa-JySgwG0DD1rMYm-gdwyJcjJlgwVPQD3ZlkJqbFFNvY4cX5injiOljpVFOHKXdi7tehY9We_vv1KYYpvhGMsE4u7o8tz2wEctdLTXT7omEq7gSdHuDgpM-h-K2RLApU8oyu8YOIqQlrqGgJ7Q8jy-jxMlU7BoZVz38FokjmkSapAAVORsbdEqPgQjeDnjaDQ5bRhxZUMSeKvvpvtVlPaeM1NI4S0R3g0qUGvX6L6qsLZqIilSQUiUaOEo8bLNBFHOxhBbocF-R-x40nSYjdjrEz60A99mz5XAA",
invalid: false,
},
}

for _, tcase := range testCases {
t.Run(tcase.name, func(t *testing.T) {
md := metadata.New(map[string]string{"authorizationJwt": tcase.token})
ctx := metadata.NewIncomingContext(context.Background(), md)

_, err := metainfo.ExtractCustomClaims(ctx)
if tcase.invalid {
require.True(t, strings.Contains(err.Error(), "unable to parse jwt token:token is unverifiable: Keyfunc returned an error"))
} else {
require.Nil(t, err)
}
})
}
}

// TODO(arijit): Generate the JWT token instead of using pre generated token.
Expand Down
6 changes: 3 additions & 3 deletions graphql/schema/schemagen.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,9 @@ func NewHandler(input string, apolloServiceQuery bool) (Handler, error) {
return nil, gqlerror.Errorf("No query or mutation found in the generated schema")
}

// If Dgraph.Authorization header is parsed successfully and JWKUrl is present
// then initialise the http client and Fetch the JWKs from the JWKUrl
if metaInfo.authMeta != nil && metaInfo.authMeta.JWKUrl != "" {
// If Dgraph.Authorization header is parsed successfully and JWKUrls is present
// then initialise the http client and Fetch the JWKs from the JWKUrls.
if metaInfo.authMeta != nil && len(metaInfo.authMeta.JWKUrls) != 0 {
metaInfo.authMeta.InitHttpClient()
fetchErr := metaInfo.authMeta.FetchJWKs()
if fetchErr != nil {
Expand Down
2 changes: 1 addition & 1 deletion graphql/schema/wrappers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@ func TestParseSecrets(t *testing.T) {
nil,
"",
nil,
errors.New("required field missing in Dgraph.Authorization: `Verification key`/`JWKUrl` `Algo` `Header` `Namespace`"),
errors.New("required field missing in Dgraph.Authorization: `Verification key`/`JWKUrl`/`JWKUrls` `Algo` `Header` `Namespace`"),
},
{
"Should be able to parse Dgraph.Authorization irrespective of spacing between # and Dgraph.Authorization",
Expand Down
7 changes: 6 additions & 1 deletion testutil/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,12 @@ func AppendAuthInfo(schema []byte, algo, publicKeyFile string, closedByDefault b
}

func AppendAuthInfoWithJWKUrl(schema []byte) ([]byte, error) {
authInfo := `# Dgraph.Authorization {"VerificationKey":"","Header":"X-Test-Auth","jwkurl":"https://www.googleapis.com/service_accounts/v1/jwk/[email protected]", "Namespace":"https://xyz.io/jwt/claims","Algo":"","Audience":["fir-project1-259e7"]}`
authInfo := `# Dgraph.Authorization {"VerificationKey":"","Header":"X-Test-Auth","jwkurl":"https://dev-hr2kugfp.us.auth0.com/.well-known/jwks.json", "Namespace":"https://xyz.io/jwt/claims","Algo":"","Audience":[ "HhaXkQVRBn5e0K3DmMp2zbjI8i1wcv2e"]}`
return append(schema, []byte(authInfo)...), nil
}

func AppendAuthInfoWithMultipleJWKUrls(schema []byte) ([]byte, error) {
authInfo := `# Dgraph.Authorization {"VerificationKey":"","Header":"X-Test-Auth","jwkurls":["https://www.googleapis.com/service_accounts/v1/jwk/[email protected]","https://dev-hr2kugfp.us.auth0.com/.well-known/jwks.json"], "Namespace":"https://xyz.io/jwt/claims","Algo":"","Audience":["fir-project1-259e7", "HhaXkQVRBn5e0K3DmMp2zbjI8i1wcv2e"]}`
return append(schema, []byte(authInfo)...), nil
}

Expand Down