Token based authentication from phx_gen_auth

Isn’t that a possible benefit though?

The cool thing about claims on JWT is that you can use them on the client and store arbitrary data (say a user object) and not have to fetch that data on client again, unless to update it regularly.

e.g. you store a token in client, and in first load you have the user’s profile already loaded, rather than doing another roundtrip to the server or storing it in local storage.

Afaik they can be decrypted, but not modified, no?

I used to read token expiry (to see if I needed to refresh) in JWT, that is not possible with Phoenix Token.

Oh, that’s a big gotcha then.

No, not really, it was my refresh strategy that was not good.

That‘s not true. You just need a binary_to_term implementation and you can read the data just fine from anywhere. By default tokens are just signed but not encrypted. There‘s nothing more secure about phoenix tokens over JWTs. They‘re just smaller and therefore less overhead if all you do is supply them to clients to send back later.

2 Likes

Well, I had this answer before for a similar question :slight_smile:

It‘s for sure not convenient to decode a phoenix token on the client side, but it‘s not impossible. As mentioned it‘s just base64 encoded binary terms. Base64 decoding is easy in js and for the binary terms there are libraries, which can do that. There‘s no security to be gained by using phoenix tokens over JWTs, just convenience in one or another way.

2 Likes

For SPAs my team just use regular cookie ; it works fine. JWT are not meant for app authentication. Projects/boilerplates with Vue or React using JWT for app auth are everywhere and it is a shame.

Though I guess if you want your API to support token authentication for server-to-server usage, then it feels clumsy to implement both authentications on the same routes. But that’s just a custom plug.

6 Likes

Yes, this is a good point and something that we should look out for on the forums.

I consider myself a bit of a pragmatist and wanted to give my perspective on the JWT for API discussion. Maybe it will be of use to you.

The biggest challenge that I have always faced when authenticating my API is how do I securely initiate the session with a few caveats:

  • The session can live securely for a long-ish period of time (let’s say 1-3 weeks)
  • The session cannot be intercepted (by typical means, security always hits a wall at some point)
  • The session can be revoked as needed

I believe that the best way to achieve these things is a cookie-based authentication mechanism. I personally use Pow, but phx_gen_auth also works well for this purpose. Heck, I even used Devise (Ruby) with a cookie-based SSO mechanism to provide the initial authorization. Cookies are great for security, but I don’t really like working with them when possible. The biggest reason is that it can be harder to implement non-web clients like mobile apps or Chrome Extensions.

That gets into the next point: how do we actually authenticate to the API itself? I think JWT is great for this purpose. If there’s an endpoint like /api/token.json, then it can return a short-lived JWT (10-20 minutes?) that is authorized based on the cookie.

All future API calls to endpoints other than /api/token.json could be authorized with that short-lived JWT. The token could be refreshed every X minutes, or when the client sees a particular HTTP code / error message. (There’s a lot of hand-waving here, that actually gets fairly complex in practice as its easy to shoot yourself in the foot).

JWT vs Phoenix Tokens? I don’t think it’s particularly that important tbh. One thing that I like about JWT is that it’s implemented in pretty much every language, so I could make an API call to a non-Elixir microservice using the same token that the Elixir service uses. JWT is such low overhead that I would typically reach for it. It might be worth pointing out that both JWT and Phoenix Token supports encrypted payloads that can’t be viewed without the secret. Pretty easy to add if you need that.

tl;dr. I like:

  • Cookie-based auth using something like Pow or phx_gen_auth
  • A token endpoint that is authorized with that cookie, if a valid user is found, sign and return a token
  • All other endpoints (or other services) use the token returned by the endpoint
  • Refresh the token on some time interval / error condition
2 Likes

A-ha! Thanks for the reply - you replied to my comment on Hacker News earlier. :slightly_smiling_face:

I ended up going with the phx.gen.auth generator and basically ripping sessions out, while keeping token generation for API authentication. The server just returns a base64 encode of the binary bytes generated as token on the server. On the client, I use next-auth to store the session token and user. It’s surprisingly elegant and I’m happy with it.

I tried Guardian but the implementation was a little messy and the generator was much cleaner. I also found Pow just wasn’t great for API use, too many open ends when it comes to things like email confirmation and whatnot.

This is my boilerplate so far, with the above customizations: GitHub - matteing/elixir-boilerplate-testground: Making a nice boilerplate in Phoenix for API development.

Here is the Plug where I ripped out sessions and tied everything together: elixir-boilerplate-testground/auth.ex at main · matteing/elixir-boilerplate-testground · GitHub

Here is the auth token creation function, with the added encoding step: elixir-boilerplate-testground/user_token.ex at main · matteing/elixir-boilerplate-testground · GitHub

I hope my repository serves as a starting point for others looking to retrofit the auth generator for APIs! I managed to do it with a good degree of success.

11 Likes

I’m implementing a similar approach for my team’s Absinthe API. I’m curious about folks’ approaches to session expiration.

The plan is to have our Remix app set a cookie to store the bearer token. I’m considering just telling the frontend team the following specs:

  1. Set your cookie expiration to <60 days (even though our backend session is set to 60 days).
  2. This bearer token may be expired at any time (if we detect a security breach). In those scenarios, it’s your responsibility to have the user re-authenticate.

Ideally, the frontend app continuously checks for the session cookie expiration and shows an alert to the user that their session will expire shortly and they will be logged-out. It’d be unfortunate if the user submitted a form only to find out they’re no longer logged-in.