---
title: 'Authentication in Elixir Web Applications with Ueberauth and Guardian: Part
  3

  '
teaser: 'Part 3 in a series on Ueberauth and Guardian authentication. Let you users
  log in and log out of their existing accounts.

  '
tags: guardian,ueberauth,authentication,phoenix,elixir,web
author: Lance Johnson
published_on: 2020-05-06
---

In previous posts we built a [basic Phoenix application with pages for
authentication] and then added [the ability for users to register for an account
using their email address and password] and be logged in after so doing. In this
post we will expand on that work by adding the ability for existing account
holders to log in and log out.

## Test driving log in and log out from the outside-in

As with our previous posts, we'll begin the work for this feature by creating a
feature test that uses a browser to emulate the way an end user will interact
with this feature. We'll want these tests to cover at least the following
requirements:

* When a user provides their email address and a correct password, the user is
  logged in and sees their profile page.
* When a user provides their email address and an incorrect password, the user
  is not logged in and sees a message that they need to try again.
* When a user provides an unknown email address and a password, the user
  is not logged in and sees a message that they need to try again.
* When a user who has already logged in visits the log in page, the user is
  redirected to their profile page.

Add the following feature tests to your project to cover those requirements:

```elixir
# test/yauth_web/features/account_sessions_test.exs
defmodule YauthWeb.AccountSessionsTest do
  use YauthWeb.FeatureCase

  import Wallaby.Query, only: [fillable_field: 1, button: 1, link: 1]
  alias Yauth.Accounts.Account
  alias Yauth.Repo

  @email_field fillable_field("account[email]")
  @password_field fillable_field("account[password]")
  @submit_button button("Log In")
  @log_out_link link("Log Out")

  setup do
    account =
      %Account{}
      |> Account.changeset(%{
        "email" => "me@example.com",
        "password" => "superdupersecret",
        "password_confirmation" => "superdupersecret"
      })
      |> Repo.insert!()

    {:ok, account: account}
  end

  test "Log in and out of an existing account", %{session: session, account: account} do
    session
    |> visit("/login")
    |> fill_in(@email_field, with: account.email)
    |> fill_in(@password_field, with: "superdupersecret")
    |> click(@submit_button)

    assert_text(session, "Hello, #{account.email}")

    click(session, @log_out_link)

    assert current_path(session) == "/login"
  end

  test "Log in with the wrong email and password", %{session: session, account: account} do
    session
    |> visit("/login")
    |> fill_in(@email_field, with: account.email)
    |> fill_in(@password_field, with: "thisisnotmypassword")
    |> click(@submit_button)

    assert_text(session, "Incorrect email or password")

    session
    |> visit("/login")
    |> fill_in(@email_field, with: "notmyemail@example.com")
    |> fill_in(@password_field, with: "superdupersecret")
    |> click(@submit_button)

    assert_text(session, "Incorrect email or password")
  end

  test "Visit log in when already logged in", %{session: session, account: account} do
    session
    |> visit("/login")
    |> fill_in(@email_field, with: account.email)
    |> fill_in(@password_field, with: "superdupersecret")
    |> click(@submit_button)

    visit(session, "/login")

    assert current_path(session) == "/profile"
  end

  test "Accessing a protected resource without logging in", %{session: session} do
    visit(session, "/profile")

    assert current_path(session) == "/login"
  end
end
```

## Logging in

As with our previous feature test for registering for an account, if we run
through the first test through a browser, we'll discover a 404 page. We are
missing our POST route to handle the submission of the log in form. Let's add
that route to our `Router`.

```elixir
# lib/yauth_web/router.ex
defmodule YauthWeb.Router do
  # ...
  scope "/", YauthWeb do
    # ...
    post "/login", SessionController, :create
    # ...
  end
  # ...
end
```

At this point our `SessionController` doesn't have a `create/2` function. That
function will receive the email address and plain text password provided by the
user nested under the key "account". As with our `RegistrationController` we can
pattern match on that key to extract the data with which this function is
concerned.

```elixir
# lib/yauth_web/controllers/session_controller.ex
defmodule YauthWeb.SessionController do
  # ...
  def create(conn, %{"account" => %{"email" => email, "password" => password}}) do
    # ...
  end
end
```

Now that we have that data bound to variables, we need to first get the account
holder from the database using that email and then ensure that the password
they provided matches the encrypted password we stored previously for that
account. As with our previous controller implementations, however, we want to
move the bulk of that work out of the controller layer and into our "contexts"
layer. Let's write the code we wish we had and then fill in the details.

```elixir
# lib/yauth_web/controllers/session_controller.ex
defmodule YauthWeb.SessionController do
  # ...
  alias Yauth.Accounts
  alias YauthWeb.Authentication

  # ...
  def create(conn, %{"account" => %{"email" => email, "password" => password}}) do
    case email |> Accounts.get_by_email() |> Authentication.authenticate(password) do
      {:ok, account} ->
        conn
        |> Authentication.log_in(account)
        |> redirect(to: Routes.profile_path(conn, :show))

      {:error, :invalid_credentials} ->
        conn
        |> put_flash(:error, "Incorrect email or password")
        |> new(%{})
    end
  end
end
```

As you can see, we pass the email data to an `Accounts.get_by_email/1` function
and pass the result of that to an `Authentication.authenticate/2` function. We
expect that function to return either the account in an `:ok` tuple or an error
in an `:error` tuple. 

We need to define those functions. Let's add the `get_by_email/1` function to
our `Accounts` context. As you may recall, we added a database index for this
field so we should have solid performance for this query.

```elixir
# lib/yauth/accounts.ex
defmodule Yauth.Accounts do
  # ...

  def get_by_email(email) do
    Repo.get_by(Account, email: email)
  end
end
```

This function will return either the account or `nil` so we'll need to handle
both in our `Authentication.authenticate/2` function. Let's define that now.

```elixir
# lib/yauth_web/authentication.ex
defmodule YauthWeb.Authentication do
  # ...
  def authenticate(%Account{} = account, password) do
    authenticate(
      account,
      password,
      Argon2.verify_pass(password, account.encrypted_password)
    )
  end

  def authenticate(nil, password) do
    authenticate(nil, password, Argon2.no_user_verify())
  end

  defp authenticate(account, _password, true) do
    {:ok, account}
  end

  defp authenticate(_account, _password, false) do
    {:error, :invalid_credentials}
  end
  # ...
end
```

There is a fair amount going on in these functions so let's walk through them
from top to bottom.

```elixir
def authenticate(%Account{} = account, password) do
  authenticate(
    account,
    password,
    Argon2.verify_pass(password, account.encrypted_password)
  )
end
```

In the first case, if we receive an `%Account{}` struct and the password, we
call through to a 3-arity function with the account, password, and the result of
calling `Argon2.verify_pass/2`. That function is used to compare a plain text
password with an encrypted password.

```elixir
def authenticate(nil, password) do
  authenticate(nil, password, Argon2.no_user_verify())
end
```

In the second function head, if we receive `nil` and the plain text password, we
call through to our 3-arity function with the result of calling
`Argon2.no_user_verify()`. This latter function always returns `false` and is
called in order to prevent certain timing attacks.

```elixir
defp authenticate(account, _password, true) do
  {:ok, account}
end

defp authenticate(_account, _password, false) do
  {:error, :invalid_credentials}
end
```

Finally, our 3-arity function returns `{:ok, account}` if the password check was
successful and `{:error, :invalid_credentials}` otherwise.

For all of this to work, however, we'll want to make some updates to our
`SessionController.new/2` function. Up to now we've been using the `conn` as our
changeset and passing a hard coded string for the action. We can now update that
to use both an account changeset and the route helper for the action.

```elixir
# lib/yauth_web/controllers/session_controller.ex
defmodule YauthWeb.SessionController do
  # ...
  def new(conn, _params) do
    render(
      conn,
      :new,
      changeset: Accounts.change_account(),
      action: Routes.session_path(conn, :create)
    )
  end
  # ...
end
```

At this point our tests for incorrect email addresses and passwords are passing
but we still have work to do for logging out and for redirecting an already
logged in user. Let's handle logging out first.

## Logging out

In order to log out, we'll want to make a DELETE request that can be handled by
our `SessionController`. First, we'll need to add a route for that request.

```elixir
# lib/yauth_web/router.ex
defmodule YauthWeb.Router do
  # ...
  scope "/", YauthWeb do
    pipe_through [:browser, :guardian, :browser_auth]
    # ...
    delete "/logout", SessionController, :delete
  end
  # ...
end
```

Next, we'll need to add that function to the `SessionController`. Similar to our
other controller functions, we'll rely on our `Authentication` context for
handling the log out and then we'll send the user back to the log in page.

```elixir
# lib/yauth_web/controllers/session_controller.ex
defmodule YauthWeb.SessionController do
  # ...
  def delete(conn, _params) do
    conn
    |> Authentication.log_out()
    |> redirect(to: Routes.session_path(conn, :new))
  end
end
```

We need to add the `log_out/1` function to our `Authentication` module.

```elixir
# lib/yauth_web/authentication.ex
defmodule YauthWeb.Authentication do
  # ...
  def log_out(conn) do
    __MODULE__.Plug.sign_out(conn)
  end
  # ...
end
```

As with our `log_in/2` function, this function calls out to a Guardian-provided
function. Recall that our `Authentication` module is our implementation module
for the Guardian `behaviour` and, as such, has access to the Guardian-provided
Plug functions. That function handles clearing the account's data from the
session.

To complete the log out functionality, we need to provide a link for our user
so they can initiate this request. First, let's replace the `<header>` of our
application layout template with one that contains a menu where we can put this
link. Note, in particular, the `method: :delete` option that we pass to the
`link` function.

```eex
<!-- lib/yauth_web/templates/layout/app.html.eex -->
<!-- ... -->
<header>
  <section class="container">
    <%= if @conn.assigns[:current_account] do %>
      <nav class="navigation" role="navigation">
        <section class="container">
          <a href="#" class="navigation-title" title="Yauth">Yauth</a>
          <ul class="navigation-list float-right">
            <li class="navigation-item">
              <%= @current_account.email %>
            </li>
            <li class="navigation-item">
              <%= link(
                "Log Out",
                to: Routes.session_path(@conn, :delete),
                method: :delete
              ) %>
            </li>
          </ul>
        </section>
      </nav>
    <% end %>
  </section>
</header>
<!-- ... -->
```

We'll also need to add some CSS to style this menu. Add the following to our
main CSS file:

```css
# assets/css/app.css
# ...
.navigation {
  background: #f4f5f6;
  border-bottom: .1rem solid #d1d1d1;
  display: block;
  height: 5.2rem;
  left: 0;
  max-width: 100%;
  position: fixed;
  right: 0;
  top: 0;
  width: 100%;
  z-index: 1;
}

.navigation .navigation-list,
.navigation .navigation-title {
  display: inline;
  font-size: 1.6rem;
  line-height: 5.2rem;
  padding: 0;
  text-decoration: none;
}

.navigation .navigation-list {
  list-style: none;
  margin-bottom: 0;
}

.navigation .navigation-item {
  float: left;
  margin-bottom: 0;
  margin-left: 2.5rem;
  position: relative;
}

main.container {
  padding-top: 7rem;
}
```

## Redirecting logged in users

At this point all of our tests should be passing with the exception of
redirecting an already logged in account. In the event a user who has already
logged in visits the "/login" route or the "/register" route, our controllers
will send that user to their profile page instead. To accomplish this the
controllers need to see if a user has already logged in. Update our `new/2`
function to the following:

```elixir
# lib/yauth_web/controllers/session_controller.ex
defmodule YauthWeb.SessionController do
  # ...
  def new(conn, _params) do
    if Authentication.get_current_account(conn) do
      redirect(conn, to: Routes.profile_path(conn, :show))
    else
      render(
        conn,
        :new,
        changeset: Accounts.change_account(),
        action: Routes.session_path(conn, :create)
      )
    end
  end
  # ...
end

# lib/yauth_web/controllers/registration_controller.ex 
defmodule YauthWeb.RegistrationController do
  # ...

  def new(conn, _) do
    if Authentication.get_current_account(conn) do
      redirect(conn, to: Routes.profile_path(conn, :show))
    else
      render(
        conn,
        :new,
        changeset: Accounts.change_account(),
        action: Routes.registration_path(conn, :create)
      )
    end
  end
  # ...
end
```

Our `Authentication` module already has the `get_current_account/1` function as
we added it in a previous post for getting the resource for our profile
controller. Here we use that function knowing that our `Pipeline` plug only
loaded the resource if the session has a valid token. The Guardian-provided
function `current_resource` either returns the account it loaded after
validating the session token or it returns `nil`. Our `SessionController` uses a
simple conditional to check for the presence of that account and redirects to
the profile page in the event it exists. Otherwise it creates the changeset and
renders the new session page.

At this point all of our tests should be passing and we've completed the feature
of allowing users to log in and out of an existing account!

## Recap

In this post we added the ability for an account holder to log in and log out of
their accounts. We did so by adding to our `Authentication` module the ability
to check a plain text password against an encrypted password and adding account
tokens to the session in the event emails and passwords matched previously
stored values. We added to our session controller logic to use the
`Authentication` module to determine what to show users. We also added the
ability to log out by leveraging the functions Guardian provides to remove
tokens from the session. 

In our [next post] we will add the ability for users to register for accounts
using their Google or Twitter accounts instead of providing an email address and
password.

[basic Phoenix application with pages for authentication]:https://thoughtbot.com/blog/authentication-in-elixir-web-applications-with-guardian-part-1
[the ability for users to register for an account using their email address and password]:https://thoughtbot.com/blog/authentication-in-elixir-web-applications-with-guardian-part-2
[next post]:https://thoughtbot.com/blog/authentication-in-elixir-web-applications-with-ueberauth-and-guardian-part-4
