Elixir /Phoenix — Lets code authentication. Todo application part 3.
With Guardian and Comeonin
Hello, and welcome to part 3 of this guide on how to write a Todo application with Elixir and the Phoenix Framework.
Let’s code authentication, Todo application part 1
Let’s code authentication, Todo application part 2
Last time, we made the user’s able to update their own personal information, and we made sure all passwords are hashed with Comeonin before stored in the database. In part 3, we’re going to focus on Guardian.
Guardian
An authentication framework for use with Elixir applications.
Guardian is based on similar ideas to Warden but is re-imagined for modern systems where Elixir manages the authentication requirements.
Guardian remains a functional system. It integrates with Plug, but can be used outside of it. If you’re implementing a TCP/UDP protocol directly, or want to utilize your authentication via channels, Guardian is your friend.
The core currency of authentication in Guardian is JSON Web Tokens (JWT). You can use the JWT to authenticate web endpoints, channels, and TCP sockets and it can contain any authenticated assertions that the issuer wants to include.
So first thing’s first, we need to add and setup Guardian in our application. Like we did with Comeonin, we start off with adding Guardian to our deps:
# mix.exs
......
defp deps do
[{:phoenix, “~> 1.1.4”},
{:postgrex, “>= 0.0.0”},
{:phoenix_ecto, “~> 2.0”},
{:phoenix_html, “~> 2.4”},
{:phoenix_live_reload, “~> 1.0”, only: :dev},
{:gettext, “~> 0.9”},
{:cowboy, “~> 1.0”},
{:comeonin, “~> 2.4”},
{:guardian, “~> 0.10.0”}]
end
.......
And we run the command mix deps.get in our console:
~todo$ mix deps.get
Running dependency resolution
Dependency resolution completed......
* Getting guardian (Hex package)
Checking package (https://repo.hex.pm/tarballs/guardian-0.10.1.tar)
Using locally cached package
.......
And shut off your server. Now, if we try to restart it, Elixir throws an error at us, saying Guardian is not configured. Open up your config file and paste the following at the end of the file:
# config/config.exsconfig :guardian, Guardian,
allowed_algos: [“HS512”],
verify_module: Guardian.JWT,
issuer: “Todo”,
ttl: { 30, :days},
verify_issuer: true,
secret_key: “<your secret guardian key>”,
serializer: Todo.GuardianSerializer
We also need to add the serializer:
# web/controllers/guardian_serializer.exdefmodule Todo.GuardianSerializer do
@behaviour Guardian.Serializer alias Todo.Repo
alias Todo.User def for_token(user = %User{}), do: {:ok, “User:#{user.id}”}
def for_token(_), do: {:error, “Unknown resource type”} def from_token(“User:” <> id), do: {:ok, Repo.get(User, id)}
def from_token(_), do: {:error, “Unknown resource type”}
end
This is a standard serializer copied from the Guardian documentation. Now that we’ve set up Guardian correctly, we can start focusing on how to actually let a user sign in and out. First, lets have another look at our routes. At this point, the whole application is accessible to everyone without signing in and out. We’ll start by adding a new pipeline to our routes, and adding a couple more plugs to the default :browser pipeline:
# web/router.ex......
pipeline :browser do
plug :accepts, [“html”]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Guardian.Plug.VerifySession
plug Guardian.Plug.LoadResource
endpipeline :browser_auth do
plug Guardian.Plug.VerifySession
plug Guardian.Plug.EnsureAuthenticated, handler: Todo.Token
plug Guardian.Plug.LoadResource
end
......
Guardian.Plug.VerifySession:
Looks for a token in the session. Useful for browser sessions. If one is not found, this does nothing.
Guardian.Plug.EnsureAuthenticated:
Looks for a previously verified token. If one is found, continues, otherwise it will call the :unauthenticated function of your handler.
Guardian.Plug.LoadResource:
The LoadResource plug looks in the sub field of the token, fetches the resource from the Serializer and makes it available via Guardian.Plug.current_resource(conn)/ Guardian Documentation
Guardian.Plug.EnsureAuthenticated needs a token handler, so it knows what to do if no verified token is found. Let’s write that now:
# web/controllers/token.exdefmodule Todo.Token do
use Todo.Web, :controller
def unauthenticated(conn, _params) do
conn
|> put_flash(:info, “You must be signed in to access this page”)
|> redirect(to: session_path(conn, :new))
end def unauthorized(conn, _params) do
conn
|> put_flash(:error, “You must be signed in to access this
page”)
|> redirect(to: session_path(conn, :new))
end
end
First, we import some useful helpers from the :controller module. This is so that we can use put_flash and redirect in our code. :unauthenticated and :unauthorized will now be handled correctly in our application.
At this point, our server will crash, since we don’t have any session routes. We’ll modify the current Todo scope, and add a second one used for authenticated users:
# web/router.ex......
scope “/”, Todo do
pipe_through :browser # Use the default browser stack get “/”, PageController, :index
resources “/users”, UserController, only: [:new, :create]
resources “/sessions”, SessionController, only: [:new, :create,
:delete]
endscope “/”, Todo do
pipe_through [:browser, :browser_auth]
resources “/users”, UserController, only: [:show, :index, :update]
end
......
In our first Todo scope, we put some changes on the user resource, and added the session routes we’re going to use very soon. the :new and :create flag in the user resource is set so that unauthorized users only can access the :new action and create a new user. We do the same thing with the session routes. In our second Todo scope, we pipe through our new pipeline that assures our users are signed in before they can access the rest of the user pages.
Cool. We can now start on our session controller. Create a new file in the controllers folder and name it session_controller.ex:
# web/controllers/session_controller.exdefmodule Todo.SessionController do
use Todo.Web, :controller def new(conn, _) do
render conn, “new.html”
end
end
We start of with the :new action, where we just render the empty sign in form. We also need to add a new view, and the corresponding template:
# web/views/session_view.exdefmodule Todo.SessionView do
use Todo.Web, :view
end
# web/templates/session/new.html.eex<h1>Sign in</h1>
<%= form_for @conn, session_path(@conn, :create), [as: :session],
fn f -> %>
<div class=”form-group”>
<%= text_input f, :email, placeholder: “Email”,
class: “form-control” %>
</div>
<div class=”form-group”>
<%= password_input f, :password, placeholder: “Password”,
class: “form-control” %>
</div>
<%= submit “Sign in”, class: “btn btn-primary” %>
<% end %>
Perfect! We got ourself a sign in form. As we know from before, the form invokes the :create action which we haven’t written yet. We should definitely look into that now:
# web/controllers/session_controller.ex
......
def create(conn, %{“session” => %{“email” => user,
“password” => pass}}) do
case Todo.Auth.login_by_email_and_pass(conn, user, pass,
repo: Repo) do
{:ok, conn} ->
logged_in_user = Guardian.Plug.current_resource(conn)
conn
|> put_flash(:info, “Innlogget”)
|> redirect(to: user_path(conn, :show, logged_in_user))
{:error, _reason, conn} ->
conn
|> put_flash(:error, “Wrong username/password”)
|> render(“new.html”)
end
enddef delete(conn, _) do
conn
|> Guardian.Plug.sign_out
|> redirect(to: "/")
end......
First, we collect the email and the password from the form, and we pass it into the module Auth with we still need to write. If the user’s email and password is correct, we collect the current user and store it in the variable logged_in_user, we then redirect the user’s to their own personal page, with a nice flash message informing them they sign in was successful. If not, the :new action gets re-rendered and they have to try again. We’ll also write a function for logging out. All it does is logging out with the help from Guardian, and redirecting to our index page.
As mentioned above, we have to check if the user’s email and password combination is correct. We’ll write a new module which we will call Auth.ex:
# web/controllers/auth.exdefmodule Todo.Auth do
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
import Plug.Conn def login(conn, user) do
conn
|> Guardian.Plug.sign_in(user, :access)
end def login_by_email_and_pass(conn, email, given_pass, opts) do
repo = Keyword.fetch!(opts, :repo)
user = repo.get_by(Todo.User, email: email) cond do
user && checkpw(given_pass, user.password_hash) ->
{:ok, login(conn, user)}
user ->
{:error, :unauthorized, conn}
true ->
dummy_checkpw()
{:error, :not_found, conn}
end
end
end
We import Comeonin.Bcrypt and Plug.Conn so that we have the necessary helpers available in our code. The login/2 function generate a new signed in token for the user, however this is not the function we invoke when trying to sign in, that would be the login_by_email_and_pass/4 function. Here, we get the correct repo, and the correct user from the repo. We then check if the user’s given pass and the password stored in the database is a match. If so, we invoke the login/2 function which signs in the user. If the given pass and password stored in the database is incorrect, we give it a :unauthorized flag, which will be handled by our Token Handler. The dummy_checkpw() is a security measure against timed attacks.
That’s it for authentication, however there are still a couple things we can do to make the application a bit easier to use for our users. Let’s put on a couple of links for signing in, signing out, and creating new users. We are going to make a partial view template and render it in our app layout:
# web/templates/layout/sign_in_sign_out.html.eex<%= if logged_in?(@conn) do %>
<%= link “Sign out”, to: session_path(@conn, :delete, :access),
method: :delete, class: “btn btn-danger” %><br>
<%= current_user(@conn).email %>
<% else %>
<%= link “Create new account”, to: user_path(@conn, :new),
class: “btn btn-danger” %>
<%= link “Sign in”, to: session_path(@conn, :new),
class: “btn btn-danger” %>
<% end %>
Our app is obviously going to crash right now, because we have no helper function called logged_in?. Why don’t we just create it right now?
# web/views/helpers.exdefmodule Todo.ViewHelper do
def current_user(conn), do: Guardian.Plug.current_resource(conn)
def logged_in?(conn), do: Guardian.Plug.authenticated?(conn)
end
To have the helpers available in our views, we need to add it to the view block in web.ex:
# web/web.ex......
def view do
quote do
use Phoenix.View, root: “web/templates” # Import convenience functions from controllers
import Phoenix.Controller, only: [get_csrf_token: 0,
get_flash: 2, view_module: 1] # Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML import Todo.Router.Helpers
import Todo.ErrorHelpers
import Todo.Gettext
import Todo.ViewHelper
end
end
......
Awesome! The application is up and running again. Now, let’s render the partial we just created:
# web/templates/layout/app.html.eex......
<header class=”header”>
<nav role=”navigation”>
<ul class=”nav nav-pills pull-right”>
<%= render “sign_in_sign_out.html”, conn: @conn %><br><br>
</ul>
</nav>
<span class=”logo”></span>
</header>
......
This won’t win any design awards, however we can now sign in and sign out correctly. Try signing in and out, and see how the menu changes depending on the session status.
When users create a new account, they aren’t automatically getting signed in. This is a very easy fix right now. We can just add one line to our :create action in user_controller.ex:
# web/controllers/user_controller.exdef create(conn, %{“user” => user_params}) do
changeset = User.registration_changeset(%User{}, user_params)
case Repo.insert(changeset) do
{:ok, user} ->
conn
|> Todo.Auth.login(user) # new line added
|> put_flash(:info, “User created!”)
|> redirect(to: user_path(conn, :index))
{:error, changeset} ->
conn
|> render(“new.html”, changeset: changeset)
end
end
This will assure us that we pipe through the authentication before we get redirected.
The users can still access other user’s personal information. To make sure users can only access and edit their own personal information, we have to do a couple of small changes to our :show and :update action in our user controller:
# web/controllers/user_controller.exdef show(conn, %{“id” => id}) do
user = Repo.get(User, id)
changeset = User.changeset(user)
cond do
user == Guardian.Plug.current_resource(conn) ->
conn
|> render(“show.html”, user: user, changeset: changeset)
:error ->
conn
|> put_flash(:info, “No access”)
|> redirect(to: page_path(conn, :index))
end
enddef update(conn, %{“id” => id, “user” => user_params}) do
user = Repo.get(User, id)
changeset = User.registration_changeset(user, user_params)
cond do
user == Guardian.Plug.current_resource(conn) ->
case Repo.update(changeset) do
{:ok, user} ->
conn
|> put_flash(:info, “User updated”)
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
conn
|> render(“show.html”, user: user, changeset: changeset)
end
:error ->
conn
|> put_flash(:info, “No access”)
|> redirect(to: page_path(conn, :index))
end
end
In both the :show and the :update action, we check if the user registered in the database is the user who is signed in. If not, they get redirected.
If you go to localhost:4000/users you get a list of all your users. If you try to access a user you’re not logged in with, the access will be denied.
That should cover how to write a simple authentication with Comeonin and Guardian, from start to end. If you have any questions, please ask below,
or continue with the forth part.
And as always thank you for reading, and have a wonderful day!
Until next time
Stephan Bakkelund Valois