Elixir /Phoenix — Lets code authentication. Todo application part 2.
With Guardian and Comeonin
Hello, and thank you for checking in.
If you haven’t read part one of this guide, you can find it here.
In part 1 we created a user controller, user model, views and templates for creating new users and listing them. We are still storing the user’s passwords in plain text, however before we start hashing them, let’s implement a way for the user to change their personal information. What we need is a new action in our user controller, the :show action:
# web/controllers/user_controller.exdef show(conn, %{“id” => id}) do
user = Repo.get(User, id)
changeset = User.changeset(user)
render(conn, “show.html”, user: user, changeset: changeset)
end
Here we get the user’s id, and store it in the user variable, collecting the correct user from the database, and rendering show.html, while having both the user and the changeset available in the view template.
Lets put a link to the :show action on each user, so that we can easily access their own, personal page:
# web/templates/user/index.html.eex<h1>Users</h1>
<table class=”table”>
<%= for user <- @users do %>
<tr>
<td><%= user.email %>
(<%= link “View”, to: user_path(@conn, :show, user) %>)
</td>
</tr>
<% end %>
</table>
Now, if we try to access any of the user pages, Phoenix raises an error:
Could not render “show.html” for Todo.UserView.........
So we’ll add a new template for the corresponding :show action:
# web/templates/user/show.html.eex<h1><%= @user.email %></h1>
<%= form_for @changeset, user_path(@conn, :update, @user),
fn f -> %>
<%= if @changeset.action do %>
<div class=”alert alert-danger”>
<p>Oops, something went wrong</p>
</div>
<% end %>
<div class=”form-group”>
<%= text_input f, :email, placeholder: “Email”,
class: “form-control” %>
<%= error_tag f, :email %>
</div>
<div class=”form-group”>
<%= password_input f, :password, placeholder: “Password”,
class: “form-control” %>
<%= error_tag f, :password %>
</div>
<%= submit “Update user”, class: “btn btn-primary” %>
<% end %>
Like the :new and :create action go hand in hand, so do the :edit or :show action and the :update action. We won’t be using the :edit action at this moment:
# web/controllers/user_controller.ex.................
def update(conn, %{“id” => id, “user” => user_params}) do
user = Repo.get(User, id)
changeset = User.changeset(user, user_params)case Repo.update(changeset) do
{:ok, user} ->
conn
|> put_flash(:info, “User updated”)
|> redirect(to: user_path(conn, :index))
{:error, changeset} ->
conn
|> render(“show.html”, user: user, changeset: changeset)
end
end
.................
As we did in the :create action, we collect the user’s id, and the new user information from the form. We can then get the correct user from the database, and prepare the changeset with the correct user and the new user information. If the update is successful, we get redirected to the user’s index page with a nice flash message telling us everything went well. If not, we render the :show action, and let the user try again.
Comeonin for hashing passwords
If we enter iEX and look up our first user, we can see that the password is stored in plain text:
iex> alias Todo.User
iex> alias Todo.Repo
iex> Repo.get(User, 1)
[debug] SELECT u0.”id”, u0.”email”, u0.”password_hash”, u0.”inserted_at”, u0.”updated_at” FROM “users” AS u0 WHERE (u0.”id” = $1) [1] OK query=81.7ms queue=10.8ms%Todo.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: “foo@bar.com”, id: 1, inserted_at: #Ecto.DateTime<2016–05–28T09:49:50Z>, password: nil, password_hash: “foobar123”, updated_at: #Ecto.DateTime<2016–05–28T20:49:12Z>}
This is not good, and it is a major security violation which we now are going to address. We will be using Comeonin, which uses Bcrypt for hashing strings. The first thing we need to do, is to add Comeonin as a new application dependency. All dependencies are listed in the mix project file — mix.exs. Let’s add Comeonin to the deps function:
# mix.exsdefp 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”}]
end
To install the new dependency, we run the command mix deps.get in our console:
~/todo$ mix deps.get
Running dependency resolution
Dependency resolution completed
comeonin: 2.4.0
* Getting comeonin (Hex package)
Checking package (https://repo.hex.pm/tarballs/comeonin-2.4.0.tar)
Using locally cached package
And that’s it. Comeonin is installed, and ready to be used. Before we do anything else, we need to restart our server. We can stop the server by pressing ctrl + c twice inside the console window our server is running in. Now, we’ll create a new changeset in our user model, that will only be used for the password:
# web/models/user.ex......
def registration_changeset(model, params) do
model
|> changeset(params)
|> cast(params, ~w(password), [])
|> validate_length(:password, min: 6)
|> put_password_hash()
end
.......
First, we pipe the new changeset through our original one, cast it, and put some validation on the password. In this particular case, we’ll validate the length, and set the minimum length of the password to 6 characters. The last thing we pipe through is the function put_password_hash() which we haven’t written yet. This will be a private function where we actually do the hashing. Let’s do that now:
# web/models/user.ex......
defp put_password_hash(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(changeset, :password_hash,
Comeonin.Bcrypt.hashpwsalt(pass)) _ ->
changeset
end
end
......
We check if the changeset is valid, and hash the password. if the changeset is incorrect, the first pattern wont match, and the case structure moves on to the next pattern. Since the variable _ matches everything, we’ll just return the changeset without doing anything to it. This will work as a fail guard.
To actually put our new changeset to use, we need to do a couple of changes to our user controller:
changeset = User.changeset(...)
This line exists in both our :create and :update actions. Update them to:
changeset = User.registration_changeset()
This way, every new user will have their password hashed, and securely stored in the database. But what about the users that are already in our database? We’ll borrow a little script from the book “Programming Phoenix”, and update all our user entries to use a properly hashed password:
iex> for u <- Repo.all(User) do
Repo.update!(User.registration_changeset(u, %{password:
u.password_hash || “temppass” }))
end
Sweet! Let’s have a look at the first user in our database:
iex> Repo.get(User, 1)
[debug] SELECT u0.”id”, u0.”email”, .........
%Todo.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: “foo@bar.com”, id: 1, inserted_at: #Ecto.DateTime<2016–05–28T09:49:50Z>, password: nil, password_hash: “$2b$12$ZAW.2PwfYbXuFc1XOL5.GOEKIXY81ee6gctpiwrXiFAcLNUj0aNSa”,
updated_at: #Ecto.DateTime<2016–05–28T21:26:10Z>}
Now would you look at that! The password is no longer stored as plain text, and we made our application a whole lot more secure.
Next time, I’ll introduce you to Guardian, and show you how you can use it to actually let a user sign in and out.
That’s it for the second part of this guide on how to create a todo application in phoenix. You can find the third part here.
Thank’s for reading, and have a wonderful day/night.
Until next time
Stephan Bakkelund Valois