I notice that there has been a thread with the similar title here: Guardian/JWT vs Phoenix.Token?, but it turned into exchanges about why and when (or should you) use JWT and JWT vs. sessions, with the sole mention of
Phoenix.Token only on the first post.
I’m starting a new thread to ask about what the title says: how is JWT and
Phoenix.Token different? The docs doesn’t seem to say anything about that. I mean, it’s great that I don’t have to add a dependency for generating API tokens if I use
Phoenix.Token, but as they say you shouldn’t roll out your own security mechanisms if you can help it. I suppose JWT is well-tested; how about
I hope someone can shed a light into this
After some digging, I’ve found the following sources of information: the issue (#699) about token auth, the corresponding PR (#820), and the
Phoenix.Token documentation itself. I’m writing this mainly as a note for myself, hoping for some clarification, and I figured it might help someone along the way.
Based on the issue, the motivation of the
Phoenix.Token module itself is the need to have a general API for generating tokens to be used for channel and API authentication. It doesn’t actually dictate how the implementation itself works. The issue specifically discussed about how the API would look like and, consequently, how it would be implemented.
There were two options for the implementation, one using Plug Tokens (through
Plug.Crypto) and the other JWT. The discussion kind of ended abruptly (possibly because a more thorough brainstorming was conducted somewhere else) stating that the chosen approach is the Plug one. I inferred that one of the reasons might be that by using Plug Tokens there’s no need to add another dependency and it works for most cases.
So, to answer the question:
Phoenix.Token (with Plug Tokens) vs. JWT, several points I noticed:
Phoenix.Token doesn’t let you specify signing algorithms (defaulting to sha256 according to this line on
MessageVerifier). JWT (for example using Joken) does.
Both encodes some form of payload into Base64, and the encoding can be read by clients (so you shouldn’t include sensitive information).
JWT allows you to encode arbitrary JSON objects as payload,
Phoenix.Token uses Erlang terms serialization so you can’t really do that (I’m not sure I fully understand this one, see this rejected PR for details). That said, the docs doesn’t really tell you what kind of data you can use as payload; I’m not sure everyone is familiar what Erlang terms are.
Other than that, I think the token generated using
Phoenix.Token and JWT behaves the same.
A question that still bugs my mind is about the
salt parameter. The docs doesn’t really specify whether you should use hardcoded strings like
"user" (and commit it to version control) or should we use randomized secrets strings for that, and how it will affect the tokens. I’d be happy to put up some PRs for the docs but as of now I don’t think I have the sufficient knowledge for that.
This means what you encode with JWT is only want can be encoded to JSON while Phoenix.Token is able to encode all terms. On the other side, JWT is interoperable (JSON is widespread) while Phoenix.Token not quite.
The salt can be hard coded. PRs to make the docs clearer are always welcome.
Actually you’re right. I can use keyword lists, maps and structs as the payload just fine (although the token would be significantly longer the larger your data is). I think I was confused by the term “terms”, and found the definition on the erlang docs:
A piece of data of any data type is called a term.
And that also translates to any data that can be represented in Elixir. The downside is that you can’t easily decode the token payload on the client using the usual JSON-related libraries.
I’m afraid that made me more confused then what is the purpose of having salt there and what is its intended use case? Is it for namespacing tokens (eg. for specific channel topics)?
Yes, we use the salt for key derivation and namespacing tokens. Calling it salt, although technically correct, is likely confusing.
namespace could be a better name.
I see, that explains it. Thanks for your answers, @josevalim
The Erlang term format (of which I’ve written libraries in other languages that implement it, it is wonderfully easy) is very compact (and gzip’s well too, data depending), thus it is likely shorter than an equivalent JSON encoding and much quicker to parse too!