In this post you’ll learn about authorizing data access in GraphQL and the unique challenges it presents.
Consider this user type of a banking app:
type User { id: ID! name: String!
# Only viewable by admins and account owners socialSecurityNumber: String!
# Only viewable by admins creditScore: Int!}
We want the socialSecurityNumber
to only be accessible to the user themselves and the bank’s admins.
We also want the creditScore
to only be accessible to the bank’s admins.
Why authorize at the field level?
Let’s explore scenarios where the User
type is used, and you’ll see why authorization must be done at the field level.
Showing the user’s profile
We want to support this query:
query { me { id name socialSecurityNumber }}
But not this, since credit scores are only accessible to admins:
query { me { id name socialSecurityNumber creditScore }}
Guarding access at the Query.me
level is not enough.
const resolvers = { Query: { async me(parent, inputs, context) { const user = await getUser(context)
// Cannot validate access to fields here return user } }}
Showing transfer recipients
Let’s say a user in the app wants to send money to another user.
They need to get a list of transfer recipients before sending money.
Providing the user ids and names should be enough for the feature to work, but there’s a problem.
A malicious user can also query the socialSecurityNumber
and creditScore
of other users which is a big privacy issue.
query { transferRecipients { id name
# Should not be allowed! socialSecurityNumber creditScore }}
This access validation is also not possible at the Query.transferRecipients
level.
const resolvers = { Query: { async transferRecipients(parent, inputs, context) { const user = await getUser(context)
const recipients = await getTransferRecipients(user.id)
// There's no way to validate access to fields here return recipients } }}
The only way to solve this problem is to authorize access at the field level.
How to do field level authorization
In order to authorize access at the field level, we need to implement resolvers at the User
type.
const resolvers = { Query: { me() { /* ... */ }, transferRecipients() { /* ... */ } }, User: { async socialSecurityNumber(parent, inputs, context) { const user = await getUser(context)
if (user.role !== 'admin' || user.id !== parent.id) { throw new Error('Unauthorized') }
return user.socialSecurityNumber },
async creditScore(parent, inputs, context) { const user = await getUser(context)
if (user.role !== 'admin') { throw new Error('Unauthorized') }
return user.creditScore } }}
This way we don’t have to worry about how the User
type is used.
Access to the fields are guaranteed to be valid.
Conclusion
GraphQL’s flexibility poses unique challenges to authorization not found in REST.
Complex applications with different roles and permissions often wants to restrict access to certain fields.
To ensure maximum security, authorization must be done at the field level.