skip to content
Samuel Edwin's Website

Securing GraphQL Access with Field Level Authorization

/ 3 min read

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.