Authentication

In real live examples most of the API’s are secured. It checks whether the client has proper permissions to read/write data. In GraphQL you will do the same. I can’t even imagine to allow anyone to add any data to our service anonymously.

Our goal

  1. Provide possibility to use email and password to sign in.
  2. Secure a query to check whether user is signed in.

The worst case scenario

The authentication/authorisation engine should support cases when the user provides the wrong credentials during sign in. Secured queries should be rejected when the user isn’t signed in. We will start by providing an implementation for both cases.

Sangria’s way to manage bad cases is to throw an Exception and catch it with the proper handler at the top level. Let’s implement our cases in the suggested way.

AuthenticationException will be used during sign in, when the provided email and password values don’t match the existing user. AuthorizationException will be thrown when a secured query is fetched without provided credentials.

Now we have to implement a custom exception handler.

A custom ExceptionHandler needs a partial function which converts the type of an exception into a HandledException. Next this exception is internally converted into proper JSON response and sent back to the client.

The last step and we’re done.

The Executor should now look like the following:

Executor.execute(
      GraphQLSchema.SchemaDefinition,
      query,
      MyContext(dao),
      variables = vars,
      operationName = operation,
      deferredResolver = GraphQLSchema.Resolver,
      exceptionHandler = ErrorHandler
    ).map(OK -> _)
      .recover {
        case error: QueryAnalysisError => BadRequest -> error.resolveError
        case error: ErrorWithResolver => InternalServerError -> error.resolveError
      }

Signing in

In the next step we will focus on the sign in action. But what do we need to implement it? Firstly we need an endpoint the user could use to authenticate. Next, we have to find a way to keep information whether the user is signed in correctly. At the end we have to check somehow whether the endpoint needs authorization.

FieldTag

Sangria can tag every field in queries. We could use these tags in many cases. In our example we can use a tag to check whether a field is secured. All we need is to create an object class which extends the FieldTag trait.

Now we can tag a field. In our example we will make addLink mutation secured. To do so, add tags property with the above implemented tag.

The field is tagged, but Sangria won’t do anything because tags are mostly informative and you have to manage the logic yourself. So it’s time to implement such logic now. Assume the scenario, when the user is logged in, Sangria will keep that information and when during execution it will meet the field tagged with an Authorised tag, it will check whether the user is signed in.

To keep information about the user we could use the MyContext class. As you probably remember you can use the same context object in every subsequent query. So it perfectly fits our case.

The currentUser is a property to keep information about the signed in user. login function is a helper function for authorisation, it responds with user when credential fits an existing user, in the other case it will throw an exception we’ve defined at the beginning of this chapter. Just note I’ve used Duration.Inf you should avoid it in production code, but I wanted to keep it simple. ensureAuthenticated checks the currentUser property and throws an exception in case it’s empty.

The last step is to provide the login mutation.

At this point you should understand most of the code above. But I have to explain how resolve works in this case. UpdateCtx is an action which takes two parameters. The first is a function responsible for producing a response. The output of first function is passed to the second function which has to respond with a context type. This context is replaced and used in all subsequent queries. In our case I use ctx.ctx.login(ctx.arg(EmailArg), ctx.arg(PasswordArg)) as a first function because I want to get User type in response. When the first function succeeds, this user will be passed to the second one and used to set the currentUser property.

At this point you can execute login mutation successfully. But createLink can still be accessible to anyone.

Middleware

Sangria provides a solution for middleware during execution. Middleware classes are executed during query execution. If there is more than one Middleware class, all of them will be executed one by one. In this way you can add logic which will be executed around a field or even around an entire query. The main advantage of such solution is to keep this logic completely separate from the business code. For example you can use it for benchmarking and turn it off on production environment. But in our case we will use Middleware to catch secured fields.

Our implementation needs to get access to the field before resolving. When the field has an Authorized FieldTag it should check whether the user is authenticated.

The last step is to add this middleware to the executor.

Recap

At this point we have secured the createLink mutation. So we have to login before. We can do it in the same query, like this:

mutation loginAndAddLink {
  login(
    email:"fred@flinstones.com",
    password:"wilmalove"
  ){
    name
  }

  createLink(
    url: "howtographql.com",
    description: "Great tutorial page",
    postedById: 2
  ){
    url
    description
    postedBy{
      name
    }
  }
}

You can experiment with the query above, check the reponse when you provide wrong email or password, or what will happen when you’ll skip entire login mutation.

the current state of files you can find under link below:

package/models.scala
AuthMiddleware.scala
DAO.scala
GraphQLSchema.scala
GraphQLServer.scala
MyContext.scala

And the repository with entire project you can find under this link.


The last words

Please assume I wanted to keep this code clear, you can extend the logic in many ways. For example you take user_id from signed in user instead of a field. You can also use token based authentication instead of including email or password in every query. You can also use FieldTags to check whether user has proper role to execute a query. Do whatever you want, I just wanted to show you paths you can follow.

That’s all. I hope this tutorial was useful for you and you’ve learnt something. Don’t forget the official documentation is always up to date and you can find there many helpful examples.

Unlock the next chapter
Which feature haven't we used to provide authentication?
ExceptionHandler
Middleware
InterfaceType
Context