Published

Phoenix GraphQL Tutorial with Absinthe: Authentication 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:

lib/my_app/guardian_serializer.ex

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:

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

1

2

3

4

5

6

7

8

9

defmodule MyApp.Repo.Migrations.AddPasswordHashToUsers do

useEcto.Migration

def change do

alter table(:users)do

add:password_hash,:string

end

end

end

Let’s run the migration:

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

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

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

casechangeset 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

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

1

2

3

4

5

defupdate(%{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:

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

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

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

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

useMyApp.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:

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

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

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

useAbsinthe.Schema.Notation

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

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

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))

casecheck_password(user,params.password)do

true->{:ok,user}

_->{:error,"Incorrect login credentials"}

end

end

defp check_password(user,password)do

caseuser 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

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:

1

2

3

4

5

mutationUserLogin{

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:

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.