Skip to content

Commit

Permalink
feat(GraphQL): Allow standard claims into auth variables (#7381)
Browse files Browse the repository at this point in the history
This PR adds support for adding `standard claims` of a `jwt` token in the `Auth Variables`.
For eg, if the token contains claims given below and the namespace given in the authorization header is `https://xyz.io/jwt/claims`:
```
{
   "https://xyz.io/jwt/claims": [
      ....
   ],
  "ROLE": "ADMIN",
  "USERROLE": "user1",
  "email": "random@example.com",
  "email_verified": true,
  "sub": "1234567890",
  "aud": "63do0q16n6ebjgkumu05kkeian",
  "iat": 1611694692,
  "exp": 2611730692
}
```
Then the auth variables will also include the rest of the given claims along with the claims provided under `https://xyz.io/jwt/claims`.
  • Loading branch information
minhaj-shakeel authored Feb 3, 2021
1 parent 3642fed commit 23c8796
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 28 deletions.
22 changes: 19 additions & 3 deletions graphql/authorization/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,12 @@ type CustomClaims struct {
jwt.StandardClaims
}

// UnmarshalJSON unmarshalls the claims present in the JWT.
// It also adds standard claims to the `AuthVariables`. If
// there is an auth variable with name same as one of auth
// variable then the auth variable supersedes the standard claim.
func (c *CustomClaims) UnmarshalJSON(data []byte) error {
// Unmarshal the standard claims first.
// Unmarshal the standard claims first
if err := json.Unmarshal(data, &c.StandardClaims); err != nil {
return err
}
Expand All @@ -291,14 +295,26 @@ func (c *CustomClaims) UnmarshalJSON(data []byte) error {

// Unmarshal the auth variables for a particular namespace.
if authValue, ok := result[authMeta.namespace()]; ok {
if authJson, ok := authValue.(string); ok {
if err := json.Unmarshal([]byte(authJson), &c.AuthVariables); err != nil {
if authJSON, ok := authValue.(string); ok {
if err := json.Unmarshal([]byte(authJSON), &c.AuthVariables); err != nil {
return err
}
} else {
c.AuthVariables, _ = authValue.(map[string]interface{})
}
}

// `result` contains all the cliams, delete the claim of the namespace mentioned
// in the Authorization Header.
delete(result, authMeta.namespace())
// add AuthVariables into the `result` map, Now it contains all the AuthVariables
// and other claims present in the token.
for k, v := range c.AuthVariables {
result[k] = v
}

// update `AuthVariables` with `result` map
c.AuthVariables = result
return nil
}

Expand Down
45 changes: 45 additions & 0 deletions graphql/e2e/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ type TestCase struct {
ans bool
result string
name string
jwt string
filter map[string]interface{}
variables map[string]interface{}
query string
Expand Down Expand Up @@ -494,6 +495,50 @@ func TestAuthOnInterfaces(t *testing.T) {
})
}
}

func TestQueryWithStandardClaims(t *testing.T) {
if metaInfo.Algo == "RS256" {
t.Skip()
}
testCases := []TestCase{
{
query: `
query {
queryProject (order: {asc: name}) {
name
}
}`,
jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjozNTE2MjM5MDIyLCJlbWFpbCI6InRlc3RAZGdyYXBoLmlvIiwiVVNFUiI6InVzZXIxIiwiUk9MRSI6IkFETUlOIn0.cH_EcC8Sd0pawJs96XPhpRsYVXuTybT1oUkluBDS8B4",
result: `{"queryProject":[{"name":"Project1"},{"name":"Project2"}]}`,
},
{
query: `
query {
queryProject {
name
}
}`,
jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjozNTE2MjM5MDIyLCJlbWFpbCI6InRlc3RAZGdyYXBoLmlvIiwiVVNFUiI6InVzZXIxIn0.wabcAkINZ6ycbEuziTQTSpv8T875Ky7JQu68ynoyDQE",
result: `{"queryProject":[{"name":"Project1"}]}`,
},
}

for _, tcase := range testCases {
queryParams := &common.GraphQLParams{
Headers: make(http.Header),
Query: tcase.query,
}
queryParams.Headers.Set(metaInfo.Header, tcase.jwt)

gqlResponse := queryParams.ExecuteAsPost(t, common.GraphqlURL)
common.RequireNoGQLErrors(t, gqlResponse)

if diff := cmp.Diff(tcase.result, string(gqlResponse.Data)); diff != "" {
t.Errorf("Test: %s result mismatch (-want +got):\n%s", tcase.name, diff)
}
}
}

func TestAuthRulesWithMissingJWT(t *testing.T) {
testCases := []TestCase{
{name: "Query non auth field without JWT Token",
Expand Down
42 changes: 17 additions & 25 deletions graphql/resolve/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,19 +167,29 @@ func TestStringCustomClaim(t *testing.T) {
test.LoadSchemaFromString(t, string(authSchema))
testutil.SetAuthMeta(string(authSchema))

// Token with string custom claim
// "https://xyz.io/jwt/claims": "{\"USER\": \"50950b40-262f-4b26-88a7-cbbb780b2176\", \"ROLE\": \"ADMIN\"}",
token := "eyJraWQiOiIyRWplN2tIRklLZS92MFRVT3JRYlVJWWJxSWNNUHZ2TFBjM3RSQ25EclBBPSIsImFsZyI6IkhTMjU2In0.eyJzdWIiOiI1MDk1MGI0MC0yNjJmLTRiMjYtODhhNy1jYmJiNzgwYjIxNzYiLCJjb2duaXRvOmdyb3VwcyI6WyJBRE1JTiJdLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5hcC1zb3V0aGVhc3QtMi5hbWF6b25hd3MuY29tL2FwLXNvdXRoZWFzdC0yX0dmbWVIZEZ6NCIsImNvZ25pdG86dXNlcm5hbWUiOiI1MDk1MGI0MC0yNjJmLTRiMjYtODhhNy1jYmJiNzgwYjIxNzYiLCJodHRwczovL3h5ei5pby9qd3QvY2xhaW1zIjoie1wiVVNFUlwiOiBcIjUwOTUwYjQwLTI2MmYtNGIyNi04OGE3LWNiYmI3ODBiMjE3NlwiLCBcIlJPTEVcIjogXCJBRE1JTlwifSIsImF1ZCI6IjYzZG8wcTE2bjZlYmpna3VtdTA1a2tlaWFuIiwiZXZlbnRfaWQiOiIzMWM5ZDY4NC0xZDQ1LTQ2ZjctOGMyYi1jYzI3YjFmNmYwMWIiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTU5MDMzMzM1NiwibmFtZSI6IkRhdmlkIFBlZWsiLCJleHAiOjQ1OTAzNzYwMzIsImlhdCI6MTU5MDM3MjQzMiwiZW1haWwiOiJkYXZpZEB0eXBlam9pbi5jb20ifQ.g6rAkPdNIJ6wvXOo6F4XmoVqqbGs_CdUHx_k7NrvLY8"
// Token with custom claim:
// "https://xyz.io/jwt/claims": {
// "USERNAME": "Random User",
// "email": "random@dgraph.io"
// }
//
// It also contains standard claim : "email": "test@dgraph.io", but the
// value of "email" gets overwritten by the value present inside custom claim.
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjM1MTYyMzkwMjIsImVtYWlsIjoidGVzdEBkZ3JhcGguaW8iLCJodHRwczovL3h5ei5pby9qd3QvY2xhaW1zIjp7IlVTRVJOQU1FIjoiUmFuZG9tIFVzZXIiLCJlbWFpbCI6InJhbmRvbUBkZ3JhcGguaW8ifX0.6XvP9wlvHx8ZBBMH9iyy49cRiIk7H6NNoZf69USkg2c"
md := metadata.New(map[string]string{"authorizationJwt": token})
ctx := metadata.NewIncomingContext(context.Background(), md)

customClaims, err := authorization.ExtractCustomClaims(ctx)
require.NoError(t, err)
authVar := customClaims.AuthVariables
result := map[string]interface{}{
"ROLE": "ADMIN",
"USER": "50950b40-262f-4b26-88a7-cbbb780b2176",
"sub": "1234567890",
"name": "John Doe",
"USERNAME": "Random User",
"email": "random@dgraph.io",
}
delete(authVar, "exp")
delete(authVar, "iat")
require.Equal(t, authVar, result)
// reset auth meta, so that it won't effect other tests
authorization.SetAuthMeta(&authorization.AuthMeta{})
Expand Down Expand Up @@ -232,18 +242,8 @@ func TestAudienceClaim(t *testing.T) {
md := metadata.New(map[string]string{"authorizationJwt": tcase.token})
ctx := metadata.NewIncomingContext(context.Background(), md)

customClaims, err := authorization.ExtractCustomClaims(ctx)
_, err := authorization.ExtractCustomClaims(ctx)
require.Equal(t, tcase.err, err)
if err != nil {
return
}

authVar := customClaims.AuthVariables
result := map[string]interface{}{
"ROLE": "ADMIN",
"USER": "50950b40-262f-4b26-88a7-cbbb780b2176",
}
require.Equal(t, authVar, result)
})
}
// reset auth meta, so that it won't effect other tests
Expand Down Expand Up @@ -340,18 +340,10 @@ func TestJWTExpiry(t *testing.T) {
md := metadata.New(map[string]string{"authorizationJwt": tcase.token})
ctx := metadata.NewIncomingContext(context.Background(), md)

customClaims, err := authorization.ExtractCustomClaims(ctx)
_, err := authorization.ExtractCustomClaims(ctx)
if tcase.invalid {
require.True(t, strings.Contains(err.Error(), "token is expired"))
return
}

authVar := customClaims.AuthVariables
result := map[string]interface{}{
"ROLE": "ADMIN",
"USER": "50950b40-262f-4b26-88a7-cbbb780b2176",
}
require.Equal(t, authVar, result)
})
}

Expand Down

0 comments on commit 23c8796

Please sign in to comment.