Updated: 2015-08-30 to support the 0.6.0 API.
For some reason, the first thing I look for when I’m building an application is authentication. For me things are interesting when there’s something going on on my apps, and that usually means letting people login.
I’ve been enjoying Elixir and so far there hasn’t been anything for authentication that suited what I wanted. Using Phoenix is so nice, and make things that are difficult or nearly impossible with other frameworks easy (looking at you Channels).
One of the things that I really like about Elixir/Eralng, is that making all elements of you application live in your application is (mostly) reasonable. Web requests, web-sockets, mail even raw TCP sockets are all on the table. Time for something new in auth.
Guardian is based on JWT. They are the self contained package of information that contains all the information you need for your authentication needs. For those unfamiliar I suggest having a look at the Registered Claim Names. These little packets of goodness can be used in a session, authorization header, passed in body params - they can even be used on raw sockets.
Lets look at what Guardian is not (for the moment).
- It does not have any requirements around your model
- It does not try to implement (or assist you to implement) checking login/emails
- It does not try to do oAuth
Not to say that strategies won’t come sometime in the future, but for the moment I’m not convinced that they’re actually within scope of the library. Guardian strives to provide mechanisms for seamless authentication across different access patterns, devices and S2S communications. How you determine that a user is who they say they are is, at the moment beyond the scope.
So what’s it good for?
Guardian provides a mechanism for verifying previously asserted claims that someone is who they say they are. Do this for browser based, api, channel, socket communications or just because you can. The initial login is trivial, it’s the other parts of the system where things get interesting.
Since Guardian is based on JWT, you can share the tokens with other systems that you trust. The same shared secret will allow another system to verify the token which is great for S2S systems. Your Elixir application can mint the credentials (JWT) and other systems can verify them without a call to your Elixir application, or they can mint credentials and have them used by your Elixir app. Screw language differences.
Ok so lets see it then
I’m going to pull the code from my demo application: PhoenixGuardian.
Add Guardian to your mix.deps:
Guardian relies on Joken for it’s JWTs. Guardian will bring it in, but you’ll need to configure it.
A couple of things to note.
config_moduleshould be set to
verify_issuerSet this to true if you want to only accept tokens generated by yourself (even if another app has your secret)
- Please have different
secret_keys per environment!
You’ll need the serializer so your app can serialize into and out of the token. Don’t worry, this is Elixir, they’re easy.
Need to support different model types? Just add a pattern match in your serializer!
So, at this point, we’re setup to use Guardian, we just need to decide where and how we’re going to apply it. I’ll show you the pieces.
Guardian comes with Plug integration. I’m going to focus on Phoenix but any plug will work.
There are three phases to Guardians plug integration.
- Verifying the token
- Loading the resource
- Requireing a verified token
These two pipelines will verify the token (if present) and load the resource if there was a verified token found. If the token isn’t there or is invalid, nothing bad happens, the load resource won’t do anything.
When we want to ensure that someone is authenticated we can ensure they have a verified token.
Ok, so there’s some stuff going on there. Lets break it down.
The Guardian.Plug.EnsureAuthenticated checks to make sure there was a valid token found. If it finds one we move on.
If the plug cannot find a verified token for the connection, it calls the
on_failure function. This function should be arity 2 and receive a Plug.Conn.t and it’s params. It’s up to this function to handle what should happen when things go south.
We could have put this plug in the pipeline, the only reason I didn’t do that
for this controller was because I wanted more control over the actions it fires
for, hence the
when not action stuff.
Ok so, this is all good and well. We’ve configured it, setup a serializer, and created our pipelines, how to sign in?
See that tiny line in the middle there.
That’s it. Once you do that, your token is generated, pumped into the connection
and session and off you go.
:csrf determines the tokens type (stored in the
:aud field). This can be
anything you want it to be (e.g. ‘token’, ‘csrf’, ‘api’, ‘oauth’ etc).
Ok so, I did say you could use this for channels right. Here it is:
When Guardian finds a valid token, it extracts the claims and the resource, and
join with them in the map. The keys to pattern match on for
authenticated joins are
When Guardian cannot verify the token, it will call
handle_guardian_auth_failure with the reason it failed.
Well, thats a whirlwind tour of Guardian as it stands today. There’s a lot more to say, but for the first post I think that will do.