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.
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
}
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.
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.
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.
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.
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.