Phoenix 1.3 JWT Auth API with Guardian JWTs, password hashing

Greetings: I just wrote a step-by-step guide on building a Phoenix 1.3 JWT Auth API with Guardian JWTs and Comeonin password hashing.

I would love some feedback, as I’m sure there are some things I missed/things I can improve!

Medium Article: Phoenix JWT Auth API with Guardian

Github Repo

Also: this is Part I of a two-part series; for my next trick, I’ll show how to build a React Native client for this API :slight_smile:

11 Likes

I went through the tutorial. Good timing. I am trying to learn how to use Guardian and this article is very comprehensive. I like how you explain things along the way. I also picked a couple of tricks. Thanks.

I do have some feedback. I hope I am not being that guy hung in every minor detail :lol: .

  1. Ecto creates databases with mix ecto.create not mix ecto.migrate.

  2. When creating Accounts.Users add in routers

  scope "/api", MyApiWeb do
    pipe_through :api
    resources "/users", UserController, except: [:new, :edit]
  end

before running mix ecto.migrate otherwise it throws CompileError about undefined
user_path/3

  1. Personally I prefer file names as lib/myApi/accounts/user.ex instead of user.ex in lib/myApi/accounts/. I think it is easier to follow.

  2. The first listing of put_pasword_hash(changeset)... and later import Comeonin.Bcrypt… trim our put_change function:… The second listing of put_password_hash has no difference from the first listing

  3. Mention that we need to alias myApi.Guardian inside our user_controller.ex, before altering the create/2 function, otherwise it tries to use the Guardian module from the guardian library

  4. Inside the create controller why do we need a with inside a with? It works with

    with {:ok, %User{} = user} <- Accounts.create_user(user_params),
         {:ok, token, _claims} <- Guardian.encode_and_sign(user) do
    # magic ...
    end

or I am missing something in the flow of the code?

  1. You create the sign_in/2 inside the UserController. Shouldn’t the session be handled by a separate SessionController?

  2. show/2 in UserController why does it need the if? The conn passes through LoadResource and EnsureAuthenticated so the user is already authenticated and loaded inside the conn. Also the EnsureAuthenticated already returns an {:error, :unauthenticated}

Also, if it doesn’t get out of article’s scope, can you please add how to use guardian_db?

Thanks again for the nice tutorial.

2 Likes

Voger I believe you already gave my library AccessPass a look…but it is worth noting part of the reason I made it in the first place was a revokable token solution without the need to call all the way to a database each call(like guardian_db)…just something to think about when using guardian_db

@jordiee
Yes I have seen it. I didn’t read the code yet. I plan to do soon. But what I don’t know is how does it handle reboots and blacklisting tokens. I plan to test it soon.

@voger

Many thanks for the thoughtful feedback and improvements, Voger! Will be implementing your improvements shortly

Re: 7. I don’t use a SessionController because this app doesn’t use sessions – it only uses JWT token exchange.

The sign_in function only calls Accounts.insert_or_update_user(changeset), which leads to Guardian.encode_and_sign(user) on success – this returns a JWT, which can be used by a client to act on the server, but it does not create a session on the server.

This is one of the great advantages of JWTs in appropriate contexts: your server doesn’t have to create and manage sessions for logged in users; your server only has to act when a client makes a request, and rather than checking the client’s session on the server to see if it should proceed, it just has to validate the token sent by the client.

You can use Guardian for sessions, using Guardian.Plug.sign_in(user) in a session login function and Guardian.Plug.VerifySession in your auth pipeline, but in the case of this API-only app, I did not find it necessary

Thanks again for the feedback! :slight_smile:

2 Likes

Yep, you can ask any questions in the other thread. Thanks!

Hi I just followed your guide. This was my first experience with the jwt . The side details you provided in the post are very well written and organised. I really enjoyed it . I think the code for guardian pipeline should be inside the router. But overall its really simple what you described. And I am waiting for its second part.

1 Like

Hi Script! Thanks for the feedback, I’ll post back in this thread when the second part is up. :slight_smile:

Regarding putting the pipeline in the router, to my eyes the code is cleaner with the pipeline in a separate file because of the lengthy Guardian.Plug.Pipeline, :otp declaration, module: declaration, error_handler: declaration, but I totally get the argument for having all pipeline logic in the router as well.

Cheers!

1 Like

@script @Voger Just published the React Native section of this tutorial in two parts (split the React Native section into two): https://medium.com/@njwest/building-a-react-native-jwt-client-efacf78b9364

Will likely make some tweaks here and there, but please have a look and let me know what you think! :slight_smile: