@auth directive status and security

That’s so weird, @arijit, because my resources look identical to what you have here. Here’s a list of everything (some may be duplicated from earlier in the conversation but I’m including it here for a collected reference).

  • I’m using the JSON-formatted config for the bottom of my schema.graphl file
  • I’m building off of the master branch for the Dgraph repo (git pull and then running the make install command)
  • I’m running Dgraph using dgraph zero --my=localhost:5080 and dgraph alpha --lru_mb=2048 --my=localhost:7080 --zero=localhost:5080 on two separate tabs of my command line and then uploading the schema using curl -X POST localhost:8080/admin/schema -d '@schema.graphql'
  • the mutation I’m execution is identical (excepting different variable values)
  • I’m not evaluating the "Audience" parameter but that was marked as optional in the documentation
  • I’m following this documentation to generate the private key in the schema authorization configuration
  • below is a collection of all of the resources I have in their current configuration

Schema (simplified):

type OwnerOrg implements Org & Location @auth(
	add: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
	]},
	query: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ or: [
			{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
			{ rule: "{ $role: { eq: \"USER_INTERNAL\" } }"},
		]},
		{ rule: """query($orgID: ID!) {
			queryOwnerOrg( filter: { id: [$orgID] } ) {
				id
			}
		}"""},
	]},
	update: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
		{ rule: """query($orgID: ID!) {
			queryOwnerOrg( filter: { id: [$orgID] } ) {
				id
			}
		}"""},
	]},
	delete: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
		{ rule: """query($orgID: ID!) {
			queryOwnerOrg( filter: { id: [$orgID] } ) {
				id
			}
		}"""},
	]},
) {
	labs: [LabOrg!]! @hasInverse(field: owner)
	storages: [StorageOrg!]! @hasInverse(field: owner)
}

interface Org {
	id: ID!
	name: String! @search(by: [hash])
	users: [User!]! @hasInverse(field: org)
	createdOn: DateTime!
	updatedOn: DateTime!
}

type User @auth(
	add: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
		{ rule: """query($orgID: ID!) {
			queryUser {
				owner( filter: { id: [$orgID] } ) {
					id
				}
			}
		}"""},
	]},
	query: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
		{ rule: """query($orgID: ID!) {
			queryUser {
				owner( filter: { id: [$orgID] } ) {
					id
				}
			}
		}"""},
	]},
	update: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
		{ rule: """query($orgID: ID!) {
			queryUser {
				owner( filter: { id: [$orgID] } ) {
					id
				}
			}
		}"""},
	]},
	delete: { and: [
		{ rule: "{ $isAuthenticated: { eq: \"true\" } }" },
		{ rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
		{ rule: """query($orgID: ID!) {
			queryUser {
				owner( filter: { id: [$orgID] } ) {
					id
				}
			}
		}"""},
	]},
) {
	owner: OwnerOrg!
	email: String! @id
	firstName: String! @search(by: [exact])
	lastName: String! @search(by: [exact])
	org: Org! @hasInverse(field: users)
	user_id: String!
}

interface Location {
	street: String! @search(by: [fulltext])
	city: String! @search(by: [fulltext])
	county: String! @search(by: [exact])
	state: String! @search(by: [exact])
	country: String! @search(by: [exact])
	zip: Int! @search
}

# Dgraph.Authorization {"VerificationKey":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt50KaKOwI1/r9yEojzVW\ncOwGTZbL7sjlUaSI25icLPF8eK1R2dbVaKTdZNtq6LAxFe+NDt2AuU7Vtqzv8GGv\nb2RP5KEgUcJyy75Yw0hT4TP3SrzDB2paCfcKHxQlTQ0pFP0SJMk4YCfq+gDqPnXQ\nCfzw+Zff29zZh5bs1lOxvAIgsu9LtH/zX6f5ASMdHV8EPWdZq6nq8KoOiMcAizDj\nrbm/qcAJP6k+ztbgtN6HdD8v6+7uIKStrYRa0BLXdJAra2uaLI4z2H22RHuzhkIu\nytxpYnxDlYTXzroSiRs/vs/dyHixT8smbEQmLoPTpflnoEZcNDXkhf0v9yVtG6NV\n1QIDAQAB\n-----END PUBLIC KEY-----","Header":"X-Auth0-Token","Namespace":"https://folivora.io/jwt/claims","Algo":"RS256"}

Auth0 Rule code:

function addAttributes(user, context, callback) {
  const claims = {
    "isAuthenticated": "true", // string because of dgraph requirement
    "role": user.app_metadata.role,
    "orgID": user.app_metadata.orgID
  };
  
  context.idToken["https://folivora.io/jwt/claims"] = claims;
  callback(null, user, context);
}

JWT contents:

{
  "https://folivora.io/jwt/claims": {
    "isAuthenticated": "true",
    "role": "USER_ADMIN",
    "orgID": "42"
  },
  "nickname": "john.forstmeier",
  ...
}

Mutation:

{
	"query": "mutation AddOwnerOrgs($input: [AddOwnerOrgInput!]!) { addOwnerOrg(input: $input) { ownerOrg { id } } }",
	"variables": {
		"input": [
			{
				"street": "street",
				"city": "city",
				"county": "county",
				"state": "state",
				"country": "country",
				"zip": 12345,
				"name": "name",
				"users": [],
				"createdOn": "2006-01-02T15:04:05",
				"updatedOn": "2006-01-02T15:04:05",
				"labs": [],
				"storages": []
			}
		]
	}
}

Header (token header is the token retrieved from the login process):

Sorry to keep harping on this particular issue - it’s just now become a full blocker on my end with being unable to use Dgraph @auth fully. The documentation and support here has been amazing I’m just not sure what I’m doing wrong. :grimacing:

I tried the exact same schema. I just changed the field for OwnerOrg. It seems to be working fine for me.

type OwnerOrg implements Org & Location @auth(
    add: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
    ]},
    query: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { or: [
            { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
            { rule: "{ $role: { eq: \"USER_INTERNAL\" } }"},
        ]},
        { rule: """query($orgID: ID!) {
        queryOwnerOrg( filter: { id: [$orgID] } ) {
        id
        }
        }"""},
    ]},
    update: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
        { rule: """query($orgID: ID!) {
        queryOwnerOrg( filter: { id: [$orgID] } ) {
        id
        }
        }"""},
    ]},
    delete: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
        { rule: """query($orgID: ID!) {
        queryOwnerOrg( filter: { id: [$orgID] } ) {
        id
        }
        }"""},
    ]},
) {
    random: String! @search(by: [hash])
}

interface Org {
    id: ID!
    name: String! @search(by: [hash])
    users: [User!]! @hasInverse(field: org)
    createdOn: DateTime!
    updatedOn: DateTime!
}

type User @auth(
    add: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
        { rule: """query($orgID: ID!) {
        queryUser {
        owner( filter: { id: [$orgID] } ) {
        id
        }
        }
        }"""},
    ]},
    query: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
        { rule: """query($orgID: ID!) {
        queryUser {
        owner( filter: { id: [$orgID] } ) {
        id
        }
        }
        }"""},
    ]},
    update: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
        { rule: """query($orgID: ID!) {
        queryUser {
        owner( filter: { id: [$orgID] } ) {
        id
        }
        }
        }"""},
    ]},
    delete: { and: [
        { rule: "{ $isAuthenticated: { eq: \"true\" } }" },
        { rule: "{ $role: { eq: \"USER_ADMIN\" } }"},
        { rule: """query($orgID: ID!) {
        queryUser {
        owner( filter: { id: [$orgID] } ) {
        id
        }
        }
        }"""},
    ]},
) {
    owner: OwnerOrg!
    email: String! @id
    firstName: String! @search(by: [exact])
    lastName: String! @search(by: [exact])
    org: Org! @hasInverse(field: users)
    user_id: String!
}

interface Location {
    street: String! @search(by: [fulltext])
    city: String! @search(by: [fulltext])
    county: String! @search(by: [exact])
    state: String! @search(by: [exact])
    country: String! @search(by: [exact])
    zip: Int! @search
}
# Dgraph.Authorization {"VerificationKey":"-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIdHCD6UCPADXXefvmamEdGKoB2NKV+E\nsqDO1H6MpoQED0QjL4iZHulVMjlFt+lwjU/ty+GG9ev/pqyk9pMzIlkCAwEAAQ==\n-----END PUBLIC KEY-----","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"RS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]}

JWT

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1626239022,
  "https://xyz.io/jwt/claims": {
    "isAuthenticated": "true",
    "role": "USER_ADMIN",
    "orgID": "42"
  },
  "nickname": "john.forstmeier"
}

Mutation

mutation AddOwnerOrgs($input: [AddOwnerOrgInput!]!) {
  addOwnerOrg(input: $input) {
    ownerOrg {
      id
    }
  }
}

Variables:

{
		"input": [{
				"street": "street",
				"city": "city",
				"county": "county",
				"state": "state",
				"country": "country",
				"zip": 12345,
				"name": "name",
				"users": [],
				"createdOn": "2006-01-02T15:04:05",
				"updatedOn": "2006-01-02T15:04:05",
				"random": "random"
      }
}

HTTP Header:

X-Test-Auth

JWT Token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTYyNjIzOTAyMiwiaHR0cHM6Ly94eXouaW8vand0L2NsYWltcyI6eyJpc0F1dGhlbnRpY2F0ZWQiOiJ0cnVlIiwicm9sZSI6IlVTRVJfQURNSU4iLCJvcmdJRCI6IjQyIn0sIm5pY2tuYW1lIjoiam9obi5mb3JzdG1laWVyIn0.H_L6MpDDtCDfdnKBqLilv3wuk9zRwLpGIwxu9_1t2gkcKCEZ7v6d_KuHtV_miEowcpFT-jAP-pAwElieYhgXKQ

I suggest you do the following steps and let me know if you are still getting the error.

  1. curl -X POST localhost:8180/admin/schema --data-binary '@schema.graphql
  2. Send the mutation request from either GraphQL client like insomnia or GQL playground. While sending the mutation request set the variables and HTTP header as mentioned above.

Also, you can print and see whether auth meta is set or not in ParseAuthMeta() in auth.go and use --data-binary flag in curl instead of -d.

1 Like

Hey, @arijit circling back to this and so far things appear to be working.

  • I kept my schema unchanged (tried it with your new random field on the OwnerOrg and also switched it back to my original fields)
  • hitting the API endpoint worked from Postman with the same payload

I did change two things:

  1. I added in the Audience field and included the expected value that I was able to pull from the JWT for the aud key
  2. I used the public key that was made available on https://jwt.io/ in the “VERIFY SIGNATURE” box instead of the one that was generated using the documentation in the Dgraph documentation

I’m not sure why the different public key seemed to work?

@forstmeier You need to use the same public-private key pair for JWT verification. Your public key is similar to your identity and if you sign the JWT with any other public-key then the JWT verification will fail. This allows for verification of the source of JWT and prevents tampering of JWT tokens.

You need to sign the JWT with the public key that was generated using documentation as you were using its private key for verification. But since you used a different public key from jwt.io authorization failed.

1 Like

@arijit okay, cool! Thank you for your help! I don’t know what to mark as the “answer” to the original question because this thread has been super valuable and I think answers several questions other users might have so feel free to mark it up however you see fit!

1 Like

Yes this thread is helpful for those of us who do not know much about JWTs and claims. Also if you are using Auth0 and want to do something similar for node.js you will need to use Auth0 “hooks”

2 Likes