in Elixir, GraphQL, Phoenix

Phoenix GraphQL Tutorial with Absinthe: Authentication with Guardian

Phoenix GraphQL Tutorial with Absinthe: Authenticating with Guardian

This is the third post in a series on using Phoenix and GraphQL to create clean and powerful API’s. In this post I will cover adding authentication to our API with Guardian. For those who are just joining us, I recommend starting with the first post before starting this one. However, you can start where we left off last time by cloning the repo and checking out the checkpoint-2 branch. If you go that route you’ll want to run $ mix ecto.setup  in order to create and populate your database (postgres) with some dummy data.

Adding Dependencies

We will be using the :comeonin  and :guardian  libraries to facilitate authorizing requests to our API. Let’s add them to our deps and application list:

Run mix deps.get  to pull in the new libraries. So, what did we just add? Let’s take a look.

Comeonin

Comeonin is a password hashing library for Elixir. It will allow us to create a password hash for our users and then subsequently check a password to see if it matches a user’s hashed password.

Guardian

Guardian is an Elixir authentication library that uses JSON Web Tokens (JWT) to authorize users. Once we’ve verified a user’s credentials with Comeonin we will use Guardian to create a JWT that can be sent to the client for subsequent authentication.

Setup Guardian

Guardian requires a little bit of setup in order to use. First, open up your config.exs  file and add the following:

Note that I’m using my applications secret_key_base  as the secret_key  for the Guardian config. The Guardian README explains how to generate a secret key specifically for the secret_key  field but it’s not necessary. You can use any super secret random string.

The next thing we need to do is add a Guardian serializer. Create a new file at lib/my_app/guardian_serializer.ex  and add the following:

With that Guardian should be completely set up.

Updating Users

We don’t currently have the ability to add a password to a user so we need to set that up now. Let’s add a migration to add a password_hash  field to users:

Now, open up the newly generated migration file and add the following migration:

Let’s run the migration:

User Model

We are now ready to add the ability to update a user with a password. First, we’ll need to update our user model. Here is what the new model looks like:

The first change is that we’ve added the :password  and :password_hash  fields to the schema so we can support them in our changeset. One thing to note is that the :password  field is a virtual field. This just means that the field can be used in a changeset for validating data but won’t be persisted to the database.

The next change is that we’ve added both an update_changeset  and a registration_changeset . The difference between the two is that the registration_changeset  requires a password while the update_changeset does not. We won’t be using the registration_changeset  in this post but will add it for later use.

The last change is that we’ve added a private function put_pass_hash . This function is run at the end of our changeset pipelines and simply hashes a password and adds the newly hashed password to the :password_hash  field.

Update User Mutation

Let’s now add an update user mutation so we can add a password to one of our users. The first change we need to make is to web/schema.ex . We will add a new input object :update_user_params  and then add the :update_user  mutation:

Now, open up web/resolvers/user_resolver.ex  and add the MyApp.UserResolver.update/2  function:

With those changes we should now be able to update a user with a password. Let’s start the server and navigate to http://localhost:4000/graphiql . We will update the Ryan Swapp user. Run the following mutation:

Awesome! Now that we have a user with a password it’s time to start adding authentication to our API.

Authenticating a GraphQL API

There are a couple different strategies for authenticating a GraphQL API. We can either authenticate all requests and require any incoming GraphQL request to have a valid JWT or we can authenticate on an operation-by-operation basis. I’m going to cover the latter since the former is covered in the Absinthe guides.

Context Plug

Our first step is to create a plug that all requests to our API will go through. If a request has a valid JWT in the Authorization header we will add the current user’s information to the Absinthe context property of the connection so that it can be passed into our resolver functions. If there is not a valid JWT in the request, no user will be added to the connection and we can create multiple resolver functions to pattern match and handle the cases when a request is authenticated or not. Create a new file at web/plugs/context.ex  and add the following code:

Like all plugs, this module defines two callback functions: init  and call . The call function runs Guardian.Plug.current_resource  on the connection and then either adds a context to the connection if a user is found or returns the connection without adding a context.

In order for Guardian.Plug.current_resource  to work properly, Guardian will need to have loaded the current user into the connection. We can make this happen by adding a couple of Guardian plugs to a new pipeline in our router along with the context plug that we just created.

Update your router with the following code:

The first change we made was to add the :graphql pipeline. This pipeline uses the Guardian.Plug.verify_header  and Guardian.Plug.load_resource  plugs as well as the new MyApp.Web.Context  plug. We have also created a new scope that uses the new pipeline and have only placed the /api route inside of it. I intentionally left the /graphiql route out so that we could continue to make requests on it without needing to add an Authorization header with a valid JWT.

Inside the GraphiQL app that we’ve been using to make requests there is an option to choose which endpoint you’d like to send requests to that is located just above the query field. By default it is set to http://localhost:4000/graphiql  but we will now change it to http://localhost:4000/api  so we can start seeing our new plug in action.

If you run the following query for posts the server should return all of our posts:

Let’s now modify our post resolver so that if a user sends a valid JWT it will return posts that belong to that user or else it will return an unauthorized error message. Make sure to add the import Ecto.Query, only: [where: 2]  line at the top of the module and then replace the old all  function with the new ones:

Open GraphiQL and give that posts query a try again. You should now get a “Not Authorized” error message. Great, we have an authenticated query! Now we need to add the ability for a user to obtain a JWT by logging in.

Login Mutation

In my first post I mentioned that GraphQL Types are flexible and can represent actual persisted data as well as virtual and temporary data. We are going to create a virtual type that represents a session so that we can return a JWT to the user after a successful login. Open up web/schema/types.ex  and make the following changes:

Our only change here is that we’ve added a :session  type with a :token  field. Although we won’t do it here, you could potentially put all kinds of session data in this type.

Now that we have a :session  type we can add a mutation for logging in. Open up web/schema.ex  and add the new mutation:

This mutation accepts two arguments: :email and :password . We’ll use these credentials to lookup and authenticate a user.

Before we add the login resolver function, let’s first add a new session model to provide us with some helpers for authenticating a user. Create a new file at web/models/session.ex  and add the following code:

As you can see, we’ve added both an authenticate  function and a check_password  function. The authenticate  function looks up a user with the provided params (which contains an email and password) and then uses the check_password  function to ensure that the provided password matches the hashed password stored on the user.

The last thing we need to do is add the login resolver. Open up web/resolvers/user_resolver.ex  and add this login function:

This resolver uses a with  block to first authenticate the user with MyApp.Session.authenticate  and then creates a JWT with Guardian.encode_and_sign  if the user was authenticated. It then returns a map representing our :session  type. Now that we have that in place we should be able to login!

Open up GraphiQL again and make sure the endpoint is set to http://localhost:4000/api . Let’s run the following mutation to get a login token:

Success! We now have a JWT that we can use to access our posts. One thing to note here is that if we had a frontend that was consuming this API (say, a React app) we would store this JWT in either local storage or a cookie and then send it with every subsequent request to the API. In a future post I’ll demonstrate building out a React frontend and use the Apollo Client library to consume our new GraphQL API. That post will cover storing the JWT and using it for requests.

In order to ensure that we can now access our posts, let’s run a query with our new token. Copy the token that was returned (if you exited the tab just run the login mutation again to get a new token) and add a new header. You can do this by clicking the “+ Add” button next to the header section of the GraphiQL editor. For the name input add “Authorization” (without the quotes of course) and for the value input add “Bearer <your token>” like this:

screen-shot-2016-12-06-at-9-20-29-am

Note that there is a space between “Bearer” and your token. Also, Make sure that you don’t have any quotes surrounding your token. Click “Ok” to add the header and we should now be ready to query our posts. Run the following query and you should now get back all the posts for the Ryan Swapp user:

Congratulations, you now have an authenticated query with the ability to authenticate any other query or mutation if you choose to do so.

Conclusion

In this post we added the ability to edit a user and created a plug to authenticate requests to our API. It was a bit longer than the other posts but hopefully I kept it concise enough to not bore you. Please let me know if you have any questions and look forward to the next post!

I’ve added the code from this tutorial to the repo and it will be under the branch checkpoint-3.