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:

mix.exs def application do [mod: {MyApp, []}, applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext, :phoenix_ecto, :postgrex, :absinthe, :absinthe_plug, :absinthe_ecto, :poison, :faker, :comeonin, :guardian]] end # Code Omitted 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.1"}, {:absinthe_ecto, git: "https://github.com/absinthe-graphql/absinthe_ecto.git"}, {:poison, "~> 2.1.0"}, {:faker, "~> 0.7"}, {:comeonin, "~> 2.5"}, {:guardian, "~> 0.13.0"}] end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def application do [ mod : { MyApp , [ ] } , applications : [ : phoenix , : phoenix_pubsub , : phoenix_html , : cowboy , : logger , : gettext , : phoenix_ecto , : postgrex , : absinthe , : absinthe_plug , : absinthe_ecto , : poison , : faker , : comeonin , : guardian ] ] end # Code Omitted 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.1" } , { : absinthe_ecto , git : "https://github.com/absinthe-graphql/absinthe_ecto.git" } , { : poison , "~> 2.1.0" } , { : faker , "~> 0.7" } , { : comeonin , "~> 2.5" } , { : guardian , "~> 0.13.0" } ] end

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:

config/config.exs # Configures Elixir's Logger config :logger, :console, format: "$time $metadata[$level] $message

", metadata: [:request_id] config :guardian, Guardian, allowed_algos: ["HS512"], # optional verify_module: Guardian.JWT, # optional issuer: "MyApp", ttl: { 30, :days }, verify_issuer: true, # optional secret_key: "Q/pRXuJQoZblGk4AIOHhMX0AkzuUpBS91hQVlO06PqrtRd/iAobc3CdBkMPDVYgc", serializer: MyApp.GuardianSerializer 1 2 3 4 5 6 7 8 9 10 11 12 13 # Configures Elixir's Logger config : logger , : console , format : "$time $metadata[$level] $message

" , metadata : [ : request_id ] config : guardian , Guardian , allowed_algos : [ "HS512" ] , # optional verify_module : Guardian . JWT , # optional issuer : "MyApp" , ttl : { 30 , : days } , verify_issuer : true , # optional secret_key : "Q/pRXuJQoZblGk4AIOHhMX0AkzuUpBS91hQVlO06PqrtRd/iAobc3CdBkMPDVYgc" , serializer : MyApp . GuardianSerializer

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:

lib/my_app/guardian_serializer.ex defmodule MyApp.GuardianSerializer do @behaviour Guardian.Serializer alias MyApp.Repo alias MyApp.User def for_token(user = %User{}), do: { :ok, "User:#{user.id}" } def for_token(_), do: { :error, "Unknown resource type" } def from_token("User:" <> id), do: { :ok, Repo.get(User, id) } def from_token(_), do: { :error, "Unknown resource type" } end 1 2 3 4 5 6 7 8 9 10 11 12 defmodule MyApp . GuardianSerializer do @ behaviour Guardian . Serializer alias MyApp . Repo alias MyApp . User def for_token ( user = % User { } ) , do : { : ok , "User:#{user.id}" } def for_token ( _ ) , do : { : error , "Unknown resource type" } def from_token ( "User:" <> id ) , do : { : ok , Repo . get ( User , id ) } def from_token ( _ ) , do : { : error , "Unknown resource type" } end

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:

$ mix ecto.gen.migration add_password_hash_to_users 1 $ mix ecto . gen . migration add_password_hash_to_users

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

priv/repo/migrations/0000_add_password_hash_to_users.exs defmodule MyApp.Repo.Migrations.AddPasswordHashToUsers do use Ecto.Migration def change do alter table(:users) do add :password_hash, :string end end end 1 2 3 4 5 6 7 8 9 defmodule MyApp . Repo . Migrations . AddPasswordHashToUsers do use Ecto . Migration def change do alter table ( : users ) do add : password_hash , : string end end end

Let’s run the migration:

$ mix ecto.migrate 1 $ mix ecto . migrate

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:

web/models/user.ex defmodule MyApp.User do use MyApp.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string has_many :posts, MyApp.Post timestamps() end def update_changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :email], [:password]) |> validate_required([:name, :email]) |> put_pass_hash() end def registration_changeset(struct, params \\ %{}) do struct |> cast(params, [:name, :email, :password]) |> validate_required([:name, :email, :password]) |> put_pass_hash() end defp put_pass_hash(changeset) do case changeset do %Ecto.Changeset{valid?: true, changes: %{password: pass}} -> put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass)) _ -> changeset end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 defmodule MyApp . User do use MyApp . Web , : model schema "users" do field : name , : string field : email , : string field : password , : string , virtual : true field : password_hash , : string has_many : posts , MyApp . Post timestamps ( ) end def update_changeset ( struct , params \ \ % { } ) do struct | > cast ( params , [ : name , : email ] , [ : password ] ) | > validate_required ( [ : name , : email ] ) | > put_pass_hash ( ) end def registration_changeset ( struct , params \ \ % { } ) do struct | > cast ( params , [ : name , : email , : password ] ) | > validate_required ( [ : name , : email , : password ] ) | > put_pass_hash ( ) end defp put_pass_hash ( changeset ) do case changeset do % Ecto . Changeset { valid ? : true , changes : % { password : pass } } -> put_change ( changeset , : password_hash , Comeonin . Bcrypt . hashpwsalt ( pass ) ) _ -> changeset end end end

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:

web/schema.ex input_object :update_user_params do field :name, :string field :email, :string field :password, :string end # Code Omitted mutation do field :update_user, type: :user do arg :id, non_null(:integer) arg :user, :update_user_params resolve &MyApp.UserResolver.update/2 end # Code Omitted end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 input_object : update_user_params do field : name , : string field : email , : string field : password , : string end # Code Omitted mutation do field : update_user , type : : user do arg : id , non_null ( : integer ) arg : user , : update_user_params resolve & MyApp . UserResolver . update / 2 end # Code Omitted end

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

web/resolvers/user_resolver.ex def update(%{id: id, user: user_params}, _info) do Repo.get!(User, id) |> User.update_changeset(user_params) |> Repo.update end 1 2 3 4 5 def update ( % { id : id , user : user_params } , _info ) do Repo . get ! ( User , id ) | > User . update_changeset ( user_params ) | > Repo . update end

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:

mutation UpdateUser { update_user(id: 1, user: {name: "Ryan Swapp", email: "ryan@ryan.com", password: "foobar"}) { id } } 1 2 3 4 5 mutation UpdateUser { update_user ( id : 1 , user : { name : "Ryan Swapp" , email : "ryan@ryan.com" , password : "foobar" } ) { id } }

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:

web/plugs/context.ex defmodule MyApp.Web.Context do @behaviour Plug import Plug.Conn def init(opts), do: opts def call(conn, _) do case Guardian.Plug.current_resource(conn) do nil -> conn user -> put_private(conn, :absinthe, %{context: %{current_user: user}}) end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 defmodule MyApp . Web . Context do @ behaviour Plug import Plug . Conn def init ( opts ) , do : opts def call ( conn , _ ) do case Guardian . Plug . current_resource ( conn ) do nil -> conn user -> put_private ( conn , : absinthe , % { context : % { current_user : user } } ) end end end

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:

web/router.ex defmodule MyApp.Router do use MyApp.Web, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :graphql do plug Guardian.Plug.VerifyHeader, realm: "Bearer" plug Guardian.Plug.LoadResource plug MyApp.Web.Context end scope "/", MyApp do pipe_through :browser # Use the default browser stack get "/", PageController, :index end scope "/api" do pipe_through :graphql forward "/", Absinthe.Plug, schema: MyApp.Schema end forward "/graphiql", Absinthe.Plug.GraphiQL, schema: MyApp.Schema end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 defmodule MyApp . Router do use MyApp . Web , : router pipeline : browser do plug : accepts , [ "html" ] plug : fetch_session plug : fetch_flash plug : protect_from_forgery plug : put_secure_browser_headers end pipeline : graphql do plug Guardian . Plug . VerifyHeader , realm : "Bearer" plug Guardian . Plug . LoadResource plug MyApp . Web . Context end scope "/" , MyApp do pipe_through : browser # Use the default browser stack get "/" , PageController , : index end scope "/api" do pipe_through : graphql forward "/" , Absinthe . Plug , schema : MyApp . Schema end forward "/graphiql" , Absinthe . Plug . GraphiQL , schema : MyApp . Schema end

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:

{ posts { title, body } } 1 2 3 4 5 6 { posts { title , body } }

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:

web/resolvers/post_resolver.ex # Add at top of module import Ecto.Query, only: [where: 2] def all(_args, %{context: %{current_user: %{id: id}}) do posts = Post |> where(user_id: ^id) |> Repo.all {:ok, posts} end def all(_args, _info) do {:error, "Not Authorized"} end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Add at top of module import Ecto . Query , only : [ where : 2 ] def all ( _args , % { context : % { current_user : % { id : id } } ) do posts = Post | > where ( user_id : ^ id ) | > Repo . all { : ok , posts } end def all ( _args , _info ) do { : error , "Not Authorized" } end

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:

web/schema/types.ex defmodule MyApp.Schema.Types do use Absinthe.Schema.Notation use Absinthe.Ecto, repo: MyApp.Repo object :user do field :id, :id field :name, :string field :email, :string field :posts, list_of(:post), resolve: assoc(:posts) end object :post do field :id, :id field :title, :string field :body, :string field :user, :user, resolve: assoc(:user) end object :session do field :token, :string end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 defmodule MyApp . Schema . Types do use Absinthe . Schema . Notation use Absinthe . Ecto , repo : MyApp . Repo object : user do field : id , : id field : name , : string field : email , : string field : posts , list_of ( : post ) , resolve : assoc ( : posts ) end object : post do field : id , : id field : title , : string field : body , : string field : user , : user , resolve : assoc ( : user ) end object : session do field : token , : string end end

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:

web/schema.ex mutation do field :update_user, type: :user do arg :id, non_null(:integer) arg :user, :update_user_params resolve &MyApp.UserResolver.update/2 end field :login, type: :session do arg :email, non_null(:string) arg :password, non_null(:string) resolve &MyApp.UserResolver.login/2 end # Code Omitted end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 mutation do field : update_user , type : : user do arg : id , non_null ( : integer ) arg : user , : update_user_params resolve & MyApp . UserResolver . update / 2 end field : login , type : : session do arg : email , non_null ( : string ) arg : password , non_null ( : string ) resolve & MyApp . UserResolver . login / 2 end # Code Omitted end

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:

web/models/session.ex defmodule MyApp.Session do alias MyApp.User def authenticate(params, repo) do user = repo.get_by(User, email: String.downcase(params.email)) case check_password(user, params.password) do true -> {:ok, user} _ -> {:error, "Incorrect login credentials"} end end defp check_password(user, password) do case user do nil -> false _ -> Comeonin.Bcrypt.checkpw(password, user.password_hash) end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 defmodule MyApp . Session do alias MyApp . User def authenticate ( params , repo ) do user = repo . get_by ( User , email : String . downcase ( params . email ) ) case check_password ( user , params . password ) do true -> { : ok , user } _ -> { : error , "Incorrect login credentials" } end end defp check_password ( user , password ) do case user do nil -> false _ -> Comeonin . Bcrypt . checkpw ( password , user . password_hash ) end end end

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:

web/resolvers/user_resolver.ex def login(params, _info) do with {:ok, user} <- MyApp.Session.authenticate(params, Repo), {:ok, jwt, _ } <- Guardian.encode_and_sign(user, :access) do {:ok, %{token: jwt}} end end 1 2 3 4 5 6 def login ( params , _info ) do with { : ok , user } < - MyApp . Session . authenticate ( params , Repo ) , { : ok , jwt , _ } < - Guardian . encode_and_sign ( user , : access ) do { : ok , % { token : jwt } } end end

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:

mutation UserLogin { login(email: "ryan@ryan.com", password: "foobar") { token } } 1 2 3 4 5 mutation UserLogin { login ( email : "ryan@ryan.com" , password : "foobar" ) { 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:

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:

{ posts { title, body } } 1 2 3 4 5 6 { posts { title , body } }

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.