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.

  • Pingback: Phoenix GraphQL Tutorial with Absinthe: Add CRUD Using Mutations - Ryan Swapp()

  • Great post! 👍

  • nano

    Awesome post, thanks for sharing. Looking forward to the article on Apollo with React

  • I thought that Repo was misspelled here => user = repo.get_by(User, email: String.downcase(params.email)) so I edited it and was running into errors for a while hahaha

    Great tutorial Ryan, I also think you missed a closing bracket here:
    def all(_args, %{context: %{current_user: %{id: id}}) do

  • Marcel van Pinxteren

    Could you have used ueberauth with it’s identity plugin? I’m not sure how it relates to Guardian.

    • I haven’t ever used ueberauth but you probably could use it. I chose to work directly with Guardian so I have more control over what is happeneing. Guardian is just one of the many libraries created by the people behind ueberauth.

  • azwildcat2

    Great post, thank you!

    P.S. I’m an attorney and almost a software developer. Save yourself. 🙂

    • Thanks! I prefer development over lawyering as well. It never hurts to have another degree to put on your wall though, right? haha

  • Charles Forcey

    Thanks Ryan for this terrific series of three posts — really easy to follow. I needed to make some version updates to everything to play nice thanks I think to Ecto 2 being released and have pasted the deps in case anyone else runs into that while following the tutorials. So appreciate your taking the time to write this up — I have been looking for useful ways to introduce elixir / phoenix / and or graphQL into our application stack and you hit all the bases!


    defp deps do
    [{:phoenix, "~> 1.2.1"},
    {:phoenix_pubsub, "~> 1.0"},
    {:phoenix_ecto, "~> 3.0"},
    {:postgrex, ">= 0.0.0"},
    {:phoenix_html, "~> 2.6"},
    {:phoenix_live_reload, "~> 1.0", only: :dev},
    {:gettext, "~> 0.11"},
    {:cowboy, "~> 1.0"},
    {:absinthe, "~> 1.2.0"},
    {:absinthe_plug, "~> 1.2.0"},
    {:absinthe_ecto, git: "https://github.com/absinthe-graphql/absinthe_ecto.git"},
    {:poison, "~> 2.2.0"},
    {:faker, "~> 0.7"},
    {:comeonin, "~> 2.5"},
    {:guardian, "~> 0.13.0"}]
    end

  • azwildcat2

    I’m new to MVC. Out of curiosity, why is the session module considered a model and not a controller?

    • GraphQL servers don’t follow the MVC pattern so you don’t really need to worry about it in this instance. A model represents a unit of data whether that is persisted data (stored in a database) or virtual data (stored temporarily in memory). So, typically a model will represent a table in a relational database or a collection in a nosql database like Mongo. However, it could also represent something virtual like a session.

      The MVC pattern is also a bit amorphous. The session model we built here is just a module with some helper functions. It probably should have gone in the lib folder with the rest of our modules.

      • azwildcat2

        Thank you!

  • Stuart Clove

    Ryan you helped me learn React with Meteor, then pointed me to Phoenix in a Meteor forum, now read my mind with these tutorials. Thanks for all the hard work. It helps so much.

    • Glad I could help! Thanks for reaching out 🙂

    • jaMbita

      Same here… we go waaay back 🙂 Thanks a lot Ryan.

  • azwildcat2

    How would you modify your code to secure individual pieces of data instead of entire tables? For instance, say you had a shopping cart table with fields “name” and “sales_price”. These fields require no authentication. But field “cost_basis” is protected. An unauthenticated user with products { name, sales_price, cost_basis} gets { data: { name: “Hat”, sales_price: 20, cost_basis: null} …} while the authenticated user gets cost_basis:10

  • abhinav

    What are your thoughts on implementing authorisation on top of this setup? So for example a user is authenticated to view only his own posts.

    Thanks a lot for writing this series! 🙂

  • jaMbita

    Extremely solid and helpful series, as usual. I don’t know how you do it but you always have it on point (You should consider having a series on udemy, your videos are equally impressive).

    Will try this out with a riak backend, don’t know how feasible that will be when it comes to associations since Riak KV is modeled differently.

    • Well, I’m kind of dumb, was hitting /graphql instead of /api…

  • James MacAulay

    This series was exactly what I needed, thanks!

  • Allen Gooch

    Great series! Tightly focused and deeply informative. Are you still planning on continuing the series with the registration system?

  • alan blount

    Great series – thanks a bunch!

    I’m trying to build pheonix+absinthe to connect to an existing meteor application, so we can convert it in components vs. a 1 time rewrite (also to get some early wins to make the case for elixir).

    Do you have any ready-made examples of Ecto –> Meteor Users schema ready to go? Or any other tools which would easy transition for Meteor devs and Meteor projects, who are ready to adopt Elixir and Phoenix, but who can not yet abandon Mongo and the prior schemas?

  • As Oxyrus said, there is a missing bracket on post_resolver.ex:

    def all(_args, %{context: %{current_user: %{id: id}}) do

  • Thanks so much for these posts, Ryan!

  • Augusto Pedraza

    Amazing series – Thanks a lot!

  • Ali Hemedy

    Hi ryan, i have a problem wherever i run my login mutation i get an error related to comeonin which says
    “module Comeonin.Bcrypt is not available”

  • Óscar Jiménez

    Hi Ryan! Great post 😉

    I’ve one question, do you know how can I protect all graphql request except some mutations like login or sing up. I tried to put this inside the context but I did found the way. I want to avoid check authentications in every query or mutation but login or sing up must be public

    Thanks, Óscar