Roll your own auth (split thread)

Just my unpopular opinion,

I think phoenix is still lack of core features. Like authentication for example. It’s confused me, why great framework such as Phoenix doesn’t have any auth built in it? ANY web app need it. Yes you can use 3rd-party libs such guardian, coherence, openmaize, etc, or even roll your own. But… Why? Srsly…

It’s like phoenix want to repeat rails mistake (yes, I think it’s a mitake) by not adding core functionality such as auth, and rely on 3rd-party lib (Devise for example).

I’m a django person, and I avoid using rails because it’s rely heavly on 3rd-party even just for basic functionality. Now I tried phonix with the hope that it does not repeat the mistake of its brother. I don’t says django is better by any means, I do still using 3rd-party lib, but only for really specific and unique feature that did not exist in core feature.

So my summary is… Phoenix is great! But still not productive enough in my opninion, still so much hassle, especialy for a newcomer.

2 Likes

Authentication is orthogonal to a web framework. Everyone has different needs in how they want to let users in. Baking it into the framework doesn’t make a lot of sense in most places. For instance one guy may want to use username/password and another wants oauth based authentication. I have only seen authentication provided by a web framework in ASP.NET and that too isn’t used by many as it doesn’t fit their needs or is overly complex.

5 Likes

I would prefer if they kept the core small. I wouldn’t mind a phoenix_auth dep, but that probably would just be in your dependency tree, only it’d be added with phoenix.new. You do realize that’s how a lot of the functionality exists right now, right?

Though, really, I don’t think the discussion should be about auth in particular. It takes, what, an hour to write it yourself which gives you full control of everything, or even less to bring in a good 3rd party lib.

I would rather have lots of 3rd party libs for a nice community than centralized everything so everyone has to use a bloated castle where no one can find anything.

1 Like

Phoenix/Rails/Django itself is a 3rd-party library.

That said I think authentication is such a crucial part of your app and I will never offload it to code that I (or my team) did not create or at least reviewed completely. My advise it to just invest some time in building your own authentication implementation, you will learn a lot from it and you will more confident in your app.

4 Likes

And now on topic: like dotdotdotPaul already said, learn as much as you can, even if you will never use the language it’s a great way to see how people are solving problems in different languages.

In the end you need to pick the one you are most productive in and that suits you the best.

1 Like

@gon782 and @hlx, could you guys please do a blog post (or guide on here) on how to roll your own auth please? It is frequently brought up and as far as I know, there are no tuts on it at present (just posts on how to use an auth library). I think it would help a lot of folk :slight_smile:

5 Likes

Already done :wink:

5 Likes

Ah nice :slight_smile:

Do you reckon it is worth doing a version without Guardian and Comeonin as well? I.e a fully roll-your-own version?

@gon782, is @hlx’s method the same as yours?

1 Like

What I do would be more like this, since I only use Comeonin and no additional deps. The whole process wouldn’t be the same, but the only real differences are details.

More specifically the layout of the system would be a bit different with today’s mindset, I think. It’s reasonable to start with an umbrella app and create the user backend first, which makes the whole “login” flow (separated from web stuff) extremely straight forward. You create your check_login functions or whatever you want to call them inside there and simply refer to them from your web bits, in your Session stuff.

I would add also that it’s helpful to add a bit in your EnsureAuth plug to save your current URL (put_session(conn, :redirect_location, conn.request_path)) before you redirect to your login page (an example can be found here). This means that you can add a redirect to that location when you successfully log in. Remember to clear the session key, though, otherwise it’ll persist through the session after logout and when the user logs in straight from the login page he’ll be redirected to an old page that redirected him previously (perhaps last time he logged in). It makes for a confusing experience.

1 Like

Doing it without Guardian is fine, in fact Guardian is entirely useless in that post as Phoenix already has Session and Token support. Comeonin implements bcrypt, which you do NOT want to try to remake yourself. Let me say that again, do NOT try to re-implement a security library yourself because you will do it wrong.

Also with OAuth, the spec sucks so much that trying to rewrite it yourself is an outright pain, ueberauth as such is very useful there.

8 Likes

Exactly my thinking. :smiley:

As for the rest, if I was doing other auth stuff I’d probably just use a library, but for simple login stuff it’s not much to really do. Refreshingly simple, for the oversight it gives you.

1 Like

I wonder if it might be worth putting up a community-created guide? This way we could discuss what might be the best way to tackle auth and come up with something that could be written into a guide and then kept up to date on the forum. Auth is probably the first thing most people will need (and need to get right) so I think it would be really good to do something like this. I nominate you, @hlx and @OvermindDL1 :lol: (or maybe @hlx would be happy to do a version of his post without Guardian?)

Isn’t there an underlying Erlang library that could be called? Or better still anything like Rails’s SecurePassword in Phoenix?

2 Likes

Erlang has good :crypt functions, but for this purpose you’d still be spinning up some manual stuff like salting and time constraints (making sure the function takes a specific amount of time even if it does nothing) and more. bcrypt has already solved that, and as per the old adage “Anyone who writes cryptography software always fails to write it well”, and that includes bcrypt, it is just the best well tested one out. :wink:

2 Likes

Without Comeonin would be a bit difficult since it only handles the Bcrypt in my post. Without Guardian would be possible, will think about it :wink:

1 Like

Guardian is only a JWT handler, Phoenix.Token can already generate secure tokens (arbitrary data, not JWT, JWT is more useful when sending secure data to a remote server, tokens are better for local verification), and Phoenix.Session can store arbitrary data for a user session. :slight_smile:

4 Likes

I am rolling my own auth at the mo and although I have read a few tutorials, they range from older to the latest version of phoenix framework, have different approaches based on the coder’s preferences and their typical app needs, and would love to be able to refer to something this community regards as a good standard for auth that can be integrated into an app or an app itself as part of an umbrella app.

If such a ‘standard’ of sorts could be written, perhaps it could become a mix phoenix.new --with-auth or mix phoenix.server type option?

And the, perhaps it could even allow package devs to work with it to extend it for umbrella apps, saas apps, etc?

Thanks! Learn something new every day :wink:

I agree that Phoenix.Token is a lightweight, dependency free JWT replacement and works nicely. I’d really love a Guardian analog for Phoenix.Token. I’m writing one myself, namespacing it under Phoenix.Token.Plug, but every code I type is kind of replicating what Guardian has already solved (verifying headers, ensuring authentication, default error handler, etc.) and my confidence goes away with it little by little :sweat_smile: Should I really be doing this…

Well they already do the same purpose in two different ways, server signing of data. ^.^

Hmm, a comparison (I might be wrong on some of this):

(Blehg Discourse has no table support still, soon-coming apparently…)

  • Both encrypt ‘data’ in a reversible form with a key known only by the server.

  • Both are decryptable/verifiable by a remote server that has the same key.

  • Phoenix.Token uses an erlang term encoding format, so it can serialize any data that exists in elixir.

  • Guardian uses JWT, so it encodes it into JSON, however you do not have direct access to the JSON in the JWT and you are restricted to a string in the JWT so whatever you encode you have to be able to encode ‘into’ and out of a string (via a Guardian serializer that you make).

  • Phoenix.Token does not have any Plugs built with it, but it is almost trivial to write your own so that is not a real issue.

  • Guardian comes with a set of Plugs, specifically:

    • Guardian.Plug.VerifySession: All it does is grab the JWT out of Phoenix’s own session storage and if it exists then it tests if it decrypts properly at all, if it does not decrypt properly then it deletes the session entry and sets and error, else it sets a value on the conn saying it was successful, trivial to replicate with Phoenix.Token, plus this is not something you should be doing with JWT (JWT is stateless secure information passing, wtf would you store it in the session instead of just storing the data directly in the session so you do not need to parse stuff?!).
    • Guardian.Plug.VerifyHeader: All it does is grab a JWT out of a header, which is also trivially done in Phoenix itself, otherwise it does the same as VerifySession, this is a more common use of JWT and Tokens in general.
    • Guardian.Plug.EnsureAuthenticated: Checks the conn to see if the :ok was put on by one of the Verify* Plugs, if it exists then it continues on, if it does not then it redirects to the specified error handler, this is also trivially done in Phoenix itself.
    • Guardian.Plug.LoadResource: It just uses the Serializer that you defined to de-serialize the data stored in the JWT if it exists, if not then it just passes on as normal, if it is successful in deserializing then it stores the result on the conn, also trivially done with Phoenix.
    • Guardian.Plug.EnsurePermissions: This one is the first non-trivial one, but still very easy. First, JWT encodes a set of permissions in it, useful for passing to another server securely via the client instead of a direct communication, however it is very limited into what it can store (basically just true/false’s, the slots of which must be specified ahead of time). So this Plug just checks that the permissions you gave to the slot exist in the JWT that was grabbed by a prior Verify* Plug, if it fails then it calls your callback to do whatever. This is also trivially done in Phoenix, and indeed in my application I have a significantly different permission setup that does not fit in the JWT style, so that would not work for me anyway.
    • Guardian.Plug.EnsureNotAuthenticated: Just ensures that no JWT exists from a Verify* step.
  • Phoenix.Token has two functions:

    • sign/4:
iex(devserver@myserver)17> e = Phoenix.Token.sign(MyServer.Endpoint, "somesalt", %{test: 42})
"<snipped-encoded-string>"
  • verify/4:
iex(devserver@myserver)18> Phoenix.Token.verify(MyServer.Endpoint, "somesalt", e)
{:ok, %{test: 42}}
  • Phoenix.Token has max-age built in, so you can verify with a max-age too, like:
iex(devserver@myserver)19> Phoenix.Token.verify(MyServer.Endpoint, "somesalt", e, max_age: 1209600)

{:ok, %{test: 42}}
iex(devserver@myserver)20> Phoenix.Token.verify(MyServer.Endpoint, “somesalt”, e, max_age: 10)
{:error, :expired}

  • Guardian also has built-in maximum age as well, however you specify it at the creation of the JWT instead of at the verification step, two different ways to do the same thing (and of course you could encode a max age in the Phoenix.Token too if you want.

  • Guardian has a substantial amount of functions, a few are for getting the information out of conn that the Plugs set (why not just use conn.assigns?), those are sign_in/4, sign_out/2, claims/2, current_token/2, current_resource/2, and it has two functions that are like Phoenix.Token’s, except called encode_and_sign/3 and decode_and_verify/1, along with a few functions to access the data on the JWT itself.

In essence, Guardian has a few plugs that you could very easily rewrite to use Phoenix.Token and has an added complexity over Phoenix.Token because it uses the JWT spec, but otherwise can hold no extra information over Phoenix.Token that Phoenix.Token cannot already hold.

However, Guardian does use JWT, so if you want to have the client pass data securely from server-to-server-that-do-not-directly-speak without worrying about tampering in the JWT spec then JWT/Guardian is useful, but otherwise I do not see it being useful in any other way. Phoenix.Token encodes to a smaller size, is faster to use and create, is built-in to Phoenix, etc…

So basically, if you need JWT to talk to a remote server that also uses JWT or vice-versa then Guardian is awesome. Otherwise Phoenix.Token appears better to use in every way.

3 Likes

I came across a recent commit to Phoenix.Token documentation which states:

The data stored in the token is signed to prevent tampering
but not encrypted. This means it is safe to store identification
information (such as user IDs) but should not be used to store
confidential information (such as credit card numbers).

The implementation is done by Plug’s MessageVerifier. Stepping through that visually, it does indeed look like it isn’t encrypted, rather the payload is base64 encoded. So I would agree with the above documentation that it’s good for IDs (which is what I’m using it for in setting up channel authentication :sweat_smile:) but not good for secrets.

EDIT: Here is a link to the current token.ex file for convenience.