I've been getting quite a few requests lately for how to Guardian, but only the simple parts - just let me get started. I usually get pretty excited about stuff that I write and I tend to want to launch straight into all the fun stuff that can be done. This post is going to attempt to outline how to write the bread an butter of Guardian. How do you integrate it into your application with just the simple stuff.
Guardian handles browser, API, channel and even socket authentication, but lets start simple. In the browser.
For the setup of your application you should include all the relevant parts in you config that can be found on the Guardian README. This post is going to assume that you have configured Guardian and implemented the Guardian Serializer for you application.
Note: this post was written at Guardian v 0.8.0
Before we proceed
Guardian looks after authenticating each request to your application. It doesn't do the initial checking of passwords or fetching information from an OAuth provider. For that you can use something like Überauth or roll your own email/password using something like Comeonin. Guardian handles each request authentication. Challenging users and confirming their credentials is up to your application. Guardian assumes that you have a user representation that you've confirmed already.
Browser Authentication
When you have a session available you'll want to get your token into the session and use that to authenticate each request. So there's a few parts to it:
- On login, once the app has confirmed the credentials - login the user to the current session
- On each request, check the credentials and fail if they're not found.
- Logout
I'm going to assume you have configured your application to use Guardian.
Login
The first part is to login the user. Lets see some code:
def login(conn, params) do
case User.find_and_confirm_password(params) do
{:ok, user} ->
conn
|> Guardian.Plug.sign_in(user)
|> redirect(to: "/")
{:error, changeset} ->
render conn, "login.html", changeset: changeset
end
end
The only “Guardian” part is the Guardian.Plug.sign_in
line. This line generates the JWT, stores it in the session (and on the assigns) and proceeds. At this point, you're “logged in”.
On Request
Each request we want to check that JWT to make sure it's valid and identify who is the user. There's 3 main parts:
- Find the token - in our case from the session
- Load the resource associated with the token
Note that these two steps will just find the info, they won't prevent access. To prevent access to non-logged in users you'll need one more step.
- Ensure authenticated - Checks that a valid token was found and bails if not.
To get this part started, we'll want a pipeline in our router. This pipeline will assume there's a session.
pipeline :browser_auth do
plug Guardian.Plug.VerifySession
plug Guardian.Plug.LoadResource
end
- Guardian.Plug.VerifySession - Finds the token in the session
- Guardian.Plug.LoadResource - Uses your serializer to load the resource from the found JWT (if it finds one).
Wire this pipeline into your scope:
scope "/", MyApp do
pipe_through [:browser, :browser_auth]
get "/logged_in_page", LoggedInController, :logged_in_page
end
At this point, Guardian will check each request and load the associated resource (user), but it will not ensure that they're logged in.
defmodule LoggedInController do
# snip
def logged_in_page(conn, params) do
user = Guardian.Plug.current_resource(conn)
# user may or may not be here.
end
end
In order to bail if a user is not logged in we have to EnsureAuthenticated.
defmodule LoggedInController do
# snip
plug Guardian.Plug.EnsureAuthenticated, handler: __MODULE__
def logged_in_page(conn, params) do
user = Guardian.Plug.current_resource(conn)
render "logged_in_page.html", user: user
end
# handle the case where no authenticated user
# was found
def unauthenticated(conn, params) do
conn
|> put_status(401)
|> put_flash("Authentication required")
|> redirect(to: "/")
end
end
The EnsureAuthenticated plug will call it's handlers unauthenticated function if no valid token was found.
The handler is just a module that implements unauthenticated/2. This could be the current module (as in this case) or a separate module. Separate modules make sense when you have a generic handling pattern.
Logout
To logout all sessions:
defmodule SessionController do
# snip
def logout(conn, _params) do
conn
|> Guardian.Plug.sign_out
|> put_flash(:info, "Logged out")
|> redirect(to: "/")
end
end
This is the main parts of authenticating with Guardian for browser sessions. The outstanding part is how to test this.
Testing
Turns out that it's pretty tricky. We need to get the JWT into the session before we make our request. Phoenix has us covered for this. I make a helper and put it into my ConnCase.
# We need a way to get into the connection to login a user
# We need to use the bypass_through to fire the plugs in the router
# and get the session fetched.
def guardian_login(user, token \\ :token, opts \\ []) do
conn()
|> bypass_through(MyApp.Router, [:browser])
|> get("/")
|> Guardian.Plug.sign_in(user, token, opts)
|> send_resp(200, "Flush the session yo")
|> recycle()
end
This will prepare the connection and flush the token into the session. To use it we'd write a test like:
test "GET /logged_in_page", %{ user: user } do
conn = guardian_login(user)
|> get("/tokens")
assert html_response(conn, 200)
end