GraphQL Authorization with Netflix DGS and Custom Directives

Ankit Joinwal
Chegg

--

GraphQL is a powerful query language that aims to simplify querying data from backend APIs. The client application can select only the information it needs to render the User Interface.

With great power comes great responsibility.

While ensuring ease of use for client applications, service developers also have a responsibility to secure their API from unauthorized access.

One of the key aspects of securing data at rest is authorizing access to protected data. Authorization means validating whether a client (who can be a user or another software application) has permission to access secured information or operation.

In this article, we will learn how to implement authorization for GraphQL API using schema directives in SpringBoot.

Goals

  • Protect part of our schema, such as fields, queries and mutations according to user permissions.
  • In addition to the user role, support custom logic for authorization decisions.
  • Develop standard re-usable and pluggable components for GraphQL service developers to quickly implementing authorization checks for their services.
  • Fail only the parts of a request that are not authorized, but return other data when possible.

Design

To demonstrate the design, assume we are building a video streaming application that allows users to buy subscriptions to highly curated video content for various courses taught in colleges.

Schema

Our schema is very simple — it contains a Video type, a User type and a Query to get Video details by a course topic.

Subscription Micro-Service

Generally, user role information is available in a micro-service that other services use to perform authorization checks via REST or gRPC lookups. Another approach is to embed user authorization information in a JWT token at a Gateway before forwarding the request to a backend service, so that services do not have to make extra remote calls to fetch user authorization information.

Here we will assume the former case (i.e. we have a micro-service that can be queried to check a user’s access to a product feature). We can use the request below to check if the user can play video.

curl --location --request POST 'http://{{api-dns-name}}:{{port}}/rest/v1/access/check' \
--header 'Content-Type: application/json' \
--header 'USER-UUID: a18c0991-eb8f-319a-84bf-57d48cbd543c' \
--data-raw '{
"asset":{
"type":"VIDEO"
"permission":"play"
}
}'

An API response would inform whether or not the user has access to play the video.

{
"httpCode": 200,
"errors": [],
"result": {
"hasAccess": true
}
}

Secured Directive

A directive decorates part of a GraphQL schema or operation with additional configuration. Each directive can be tied to a behavior that allows us to run custom logic on the fields annotated with that directive. We will define a secured directive as seen below:

directive @secured(requires : String!) on FIELD_DEFINITION | OBJECT

We will now use the directive on our Schema to secure the playbackToken from unauthorized access.

Our playbackToken is now annotated with directive. The requires attribute of directive refers to a Spring bean (authFunction)’s method hasOffer, which returns true or false based on the user permission defined in our subscription micro-service.

We are using SpEL expression in the requires attribute. #userUuid is an expression variable, which will be replaced by the USER-UUID header in the request.

The benefits of this design are:

  • It’s a declarative and not imperative method of authorization.
  • Authorization logic is abstracted from GraphQL resolvers.
  • Looking at schema, we can easily understand authorization rules for our data.
  • We can understand how GraphQL works and intercepts resolvers and data fetchers before the GraphQL engine executes resolvers.

Implementation

Now let’s code the building blocks for our design to work.

Tech Stack

We will be using SpringBoot and Netflix DGS for building our video service.

Secured Directive Wiring

We will create a SchemaDirectiveWiring to define behavior for our directive. This is required for the GraphQL engine to intercept the query execution before data fetchers are called for a field.

Secured Directive Expression Evaluator

The requires attribute of secured directive is a SpEL expression because we want to use Spring managed bean AuthFunctions in the expression. Therefore, we require a component that evaluates the entire expression in the requires attribute.

Registering the Directive Wiring

We will now register the directive wiring with the GraphQL engine so the authorization logic can be invoked before resolvers are called for protected fields.

AuthFunction

This is a Spring managed component that contains logic to interact with our subscription micro-service.

We are using a mock SubscriptionClient to return user permissions from an in-memory map.

And that’s it! Our application is ready.

Code Repository

For reference, you can find the complete code here.

Testing It Out

Run the application and open this URL in the browser.

Try the below request without passing the USER-UUID header.

query{   
getVideoForTopic(topic:"Physics"){
title
description
url
playbackToken
}
}

The application should return the below error:

{
"errors": [
{
"message": "Exception while fetching data (/playbackToken): not authorized",
"locations": [
{
"line": 10,
"column": 5
}
],
"path": [
"getVideoForTopic",
"playbackToken"
],
"extensions": {
"errorType": "UNAUTHORIZED_ACCESS",
"message": "errors.unauthorizedAccess",
"classification": "DataFetchingException"
}
}
],
"data": {
"getVideoForTopic": {
"title": "The Map of Physics",
"description": "The Map of Physics",
"url": "https://www.youtube.com/watch?v=ZihywtixUYo",
"playbackToken": null
}
}
}

Notice that under data, playbackToken is null. There is a corresponding error for playbackToken, informing the client that it is not authorized to access playbackToken.

After that, add the below header to request and try again.

"USER-UUID":"a18c0991-eb8f-319a-84bf-57d48cbd543c"

API should now respond with a playbackToken, along with other data.

Conclusion — Extending AuthFunction

As seen here, we can refer to any Spring managed bean’s method in our secured directive expression. This means we can write any custom logic in a Spring managed bean and use it with secured directive.

Happy (secure) coding!

Let us know if you have thoughts or questions in the comments! For more great content, check out Chegg Engineering.

--

--

Ankit Joinwal
Chegg
Writer for

Lets simplify software architecture with clear reasoning