Authorization is the mechanism that controls who can do what on which resource in an application. Although it is a critical part of an application, there are limited resources available on how to build authorization into an app effectively. In this post, I’ll be illustrating how to set up authorization in a GraphQL API using a custom directive and Oso, an open-source authorization library. This tutorial covers the NodeJS variant of Oso, but it also supports Python and other languages.
There are a number of users and each of them belongs to one or more user groups. The groups are guest, member and admin. Also a user can be given escalated permission on one or more projects if he/she belongs to a certain project user group (e.g. contributor).
Depending on the membership, users have varying levels of permission on user, project and indicator resources. Specifically
All users can be fetched if a user belongs to the admin user group.
A project or all permitted projects can be queried if a user belongs to the admin or member user group or to the contributor user project group.
For a project record, the contract_sum field can be queried only if a user belongs to the admin user group or contributor user project group.
The project status can be updated if a user belongs to the admin user group or contributor user project group.
All permitted project indicators can be fetched if a user belongs to the admin user group or contributor user project group.
Permission Specification on Directive
A directive decorates part of a GraphQL schema or operation with additional configuration. Tools like Apollo Server (and Apollo Client) can read a GraphQL document’s directives and perform custom logic as appropriate.
A directive can be useful to define permission. Below shows the type definitions used to meet the authorization requirements listed above. For example, the auth directive (@auth) is applied to the project query where admin and member are required for the user groups and contributor for the project user group.
Policy Building Using Oso
Oso is a batteries-included library for building authorization in your application. Oso gives you a mental model and an authorization system – a set of APIs built on top of a declarative policy language called Polar, plus a debugger and REPL – to define who can do what in your application. You can express common concepts from “users can see their own data” and role-based access control, to others like multi-tenancy, organizations and teams, hierarchies and relationships.
An authorization policy is a set of logical rules for who is allowed to access what resources in an application. For example, the policy that describes the get:project action allows the actor (user) to perform it on the project resource if he/she belongs to required user or project groups. The actor and resource can be either a custom class or one of the built-in classes (Dictionary, List, String …). Note methods of a custom class can be used instead of built-in operations as well.
The auth directive collects the user and project group configuration on an object or field definition. Then it updates the user object in the context and passes it to the resolver. In this way, policy enforcement for queries and mutations can be performed within the resolver and it is more manageable while the number of queries and mutations increases.
On the other hand, the policy of an object field (e.g. contract_sum) is enforced within the directive. It is because, once a query (e.g. project) or mutation is resolved and its parent object is returned, the directive is executed for the field with different configuration values.
The Oso object is instantiated and stored in the context. Then a policy can be enforced with the corresponding actor, action and resource triples. For list endpoints, different strategies can be employed. For example, the projects query fetches all records but returns only authorized records. On the other hand, the indicators query is set to fetch only permitted records, which is more effective when dealing with sensitive data or a large amount of data.
Apollo Studio can be used to query the example API. Note the server is running on port 5000 and it is expected to have one of the following values in the name request header.
user group: guest
user group: member
user project group: contributor of project 1 and 3
user group: admin
user group: guest
user project group: contributor of project 1, 3, 5, 8 and 12
The member user can query the project thanks to her user group membership. Also, as the user is a contributor of project 1 and 3, she has access to contract_sum.
The query returns an error if a project that she is not a contributor is requested. The project query is resolved because of her user group membership while contract_sum turns to null.
The contributor user can query all permitted projects without an error as shown below.
In this post, it is illustrated how to build authorization in a GraphQL API using a custom directive and an open source authorization library, Oso. A custom directive is effective to define permission on a schema, to pass configuration to the resolver and even to enforce policies directly. The Oso library helps build policies in a declarative way while expressing common concepts. Although it’s not covered in this post, the library supports building common authorization models such as role-based access control, multi-tenancy, hierarchies and relationships. It has a huge potential! I hope you find this post useful when building authorization in an application.