Taming Github OAuth integrations in Phoenix with the help of Ueberauth and Guardian (but then actually no just Ueberauth)
Tags: elixir, phoenixframework, oauth, usersessions
Published: 2021-09-15 19:54:43

1. [The problem comes into focus](#the-problem-comes-into-focus)
1. [Caturday afternoon accomplishments](#caturday-afternoon-accomplishments)
1. [Creating your app in Github: an intermission](#an-intermission)
1. [This Tutorial: The Code Parts](#this-tutorial-the-code-parts)
	1. [Package dependencies](#dependencies)
	1. [`YourAppWeb.Router`](#router)
	1. [Controllers, Views, and Templates](#controllers-views-and-templates)
		1. [In which Ueberauth plays gatekeeper in more ways than one](#in-which-ueberauth-plays-gatekeeper-in-more-ways-than-one)
		1. [Not done quite yet…](#youre-not-done-quite-yet-though)
1. [🎵 Going auth the rails on a crazy train 🎵](#going-auth-the-rails)
	1. [`UserAuth`](#userauth)
		1. [`.current_user/1`](#current_user-1)
		1. [`.log_in/2`](#log_in-2)
		1. [`.log_out/2`](#log_out-2)
		1. [`.require_signin/2`](#require_signin-2)
		1. [`.fetch_current_user/2`](#fetch_current_user-2)
	1. [`YourAppWeb.Endpoint`](#yourappweb-endpoint)
	1. [`config/*.exs`](#config-exs)
1. [You made it!](#you-made-it)

You're working on your super-slick Phoenix app, and early on, determine that it  wouldn't make any sense for you to build the thing without integrating with GitHub. After all, your app *is* for developers, and developers *do* use GitHub, unless they're using GitLab or BitBucket, in which case they *still* use something, and you're gonna get around to building out those OAuth use cases too, it's just first let's focus on the biggest piece of the pie, because you have no money, and pie sounds way more appetizing than malnourishment right now.

So you go online and check around for `phoenix github oauth integration` and oh, look, it's a blog post from thoughtbot on that very subject. You remember your interview with thoughtbot in late 2017 fondly, and though your nagging worry that [candidly sharing your plans for that weekend when they asked](https://youtu.be/uGgJiOa7LzQ?t=162) was possibly the reason they passed on making you an offer,[^trip] you hold yourself above grudges, and definitely harbor nothing of the sort against their dumb stupid faces.[^faces]

[^trip]: No, I didn't go during Pride, and I have no reason to really suspect this was why I wasn't offered a job. It could've been anything; they probably had more than a dozen strong candidates, and I wasn't among the ones who stood out. I consider myself lucky to have even been considered for a role at thoughtbot, and I hope they're going strong through this pandemic.
[^faces]: Again, I have the utmost respsect for the people at thoughtbot and the work they do. I was kind of annoyed at the structure of these blog posts, but their commitment to readable, well-documented, and efficient test-driven code is an admirable rarity in software. If you can't guess just yet, I like my jokes how I like my summers: Dry, abrasive, and long enough to make you wonder if we're doing enough as a species to handle our carbon footprint. ([We're not.](https://www.forbes.com/sites/jamesconca/2021/08/16/latest-ipcc-report-predicts-disasteryet-again-but-not-much-will-happenyet-again/))

As you read through the post you discover, to your annoyance, that not all of the information you'll need in order to build what you're trying to build is given to you in one solid block of practical information and code examples. You also glean from the words "Part 4" in the title that the practical information you crave might not even be contained in one solid _blog post_ and that you are about to scavenge, ratlike, through no fewer than three (3) other posts in order to have the full working mechanism that is user authentication in Phoenix using Ueberauth + Guardian.

You acquiesce, and trudge through the four-post walkthrough, acquainting yourself with Ueberauth and Guardian along the way. It bugs you that a number of the settings you end up configuring for Guardian seem both esoteric (_"I need to implement `resource_for_token`, which takes a `resource` and throws away `_claims`, but... then I retrieve the `resource` **from** `claims` later on? In `resource_from_claims`? Why is it in the map under the key `"sub"`?"_)[^wait], yet somehow redundant (_"I have to create `YourApp.Authentication.Pipeline` module that `use`s `Guardian.Plug.Pipeline`, then give it references to the `otp_app` it should already be part of, a custom `ErrorHandler`, and module it's already namespaced in?"_), but it's the most popular Hex package that promises OAuth 2.0 support, and it even uses those hot new JWTs that're all the [rage](https://www.ducktypelabs.com/review-stop-using-jwt-for-sessions/) these [days](https://gist.github.com/samsch/0d1f3d3b4745d778f78b230cf6061452). Once everything's in place, you feel more or less like you've successfully implemented an OAuth registration workflow, with all of the "bells and whistles" of session persistence, logging out, and even logging back in again, all painlessly slotted into your middleware workflow through the magic of Plug's `pipeline`s and `plug`s.

[^wait]: _"Wait, does **Guardian** know I went to Palm Springs?"_

## The problem comes into focus

Until, of course, you try to configure the session's expiration time – which received no (0) attention in any of the four (4) posts you were following along with – and you discover that not only do Guardian's configuration settings resemble the esoteric scratchings of druid runes, Guardian's documentation gives you about as much insight into their proper usage, as you personally have into the proper usage of esoteric druid runes, and the task of implementing the session expiration gets derailed almost as soon as you begin flipping through Guardian's [hexdocs pages](https://hexdocs.pm/guardian/readme.html).

During a brief out-of-body experience, you chuckle about the schadenfreude in which you find yourself currently steeped, and once back in your body you commence spelunking through Guardian's code, hoping in vain that it offers some better insights when how it works is laid out in front of you. But you stop when your subconscious rings a small bell in the middle side of your brain, and the sudden joy of epiphany informs you that parts of your `/lib` folder now bear a striking resemblance to the code blocks you saw reading through the section of Chris McCord, José Valim, and Bruce Tate's book, [_Programming Phoenix ≥ 1.4_](https://pragprog.com/titles/phoenix14/programming-phoenix-1-4/) about building an authentication and authorization pipeline for your app. In fact, you're pretty sure that just a few small adjustments would be all you'd need before you could mercilessly tear Guardian out of your package dependencies completely and still see your code run perfectly fine.[^jwt]

[^jwt]: There are benefits to having middleware like Guardian handle your incoming and outgoing JWT credentials, **if** you need to support JWTs. If you do, then go with Guardian, and godspeed. If you're only  using them for [session management](https://fly.io/blog/api-tokens-a-tedious-survey/), though, you [really don't need](http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/), and [definitely shouldn't use JWTs](https://evertpot.com/jwt-is-a-bad-default/)

You get the coffee brewing, put on your [Getting Stuff Done music](https://youtu.be/GwoKPU1K-88), and hammer out code like a cat in a GIF.

<figure align="center">
<img alt="A looping gif of three cats sitting upright at computers, slapping the keys on the keyboard. One of them is wearing pants and a striped short sleeve shirt." src="https://media4.giphy.com/media/ule4vhcY1xEKQ/giphy.gif"/>
<figcaption>Literally like any/all of these fine feline folx</figcaption>
</figure>

## Caturday afternoon accomplishments

After an hour or two[^hour] [^or-two], you lean back at your desk, sip your last bit of coffee, and admire the mature tone of your expertly-crafted commit message, signaling your triumph over user session expiration:

[^hour]: having worked on everything around user authentication for a good solid few days' worth of work

[^or-two]: which was spread over a few months as you went through what you are sure a psychiatrist would classify as "just a harmless bit of mild depression"

```
commit 144a2a5ca5104b914c8ffc264b2cef79bd38e781 (HEAD -> github_oauth_workflow)
Author: You <you@you.you>
Date:   Sat Apr 17 15:03:04 2021 +0000

    Suck it, backstabbing Guardian jerk. DELETED!!
    
    Also session expirationisimplemented or something
```

…but especially signaling your triumph over Guardian.

One `git push`, pull request, and rebase-merge later, and your code is there on your repo's main branch, proud and ready to guide users through an absurdly streamlined authorization workflow from start to finish. And all in all, it's not really as complex as that thoughtbot blog had you worried it'd be. <br id="an-intermission" />

Of course, the prelude to all of this was setting up your app in Github, which you did once for the dev environment, and once again for staging. You'll do it one more time once you've made your first push to prod, but as to when that may come to fruition, _qui peut dire? [^dire]_ ¯\\\_(ツ)_/¯

[^dire]: "who can say?" en Francais.

{% details Creating your app listing in Github (An Intermission) %}
1. On github.com, go to your "Settings" page
1. Find the "Developer Settings" link in the sidebar
1. Click "Github Apps", then "New Github App"
1. Fill out a good name for your app. Something descriptive, but also very opaque or even random, if you're still operating in stealth mode, and then also denoting (to yourself, mainly) that this is the dev version of the Github listing; something against which you can prototype and test new features with fuzzed data, or data from your own personal projects. Something like… `your_app-dev`.
1. Run `brew install --cask ngrok` (or your preferred local-tunneling daemon)[^daemons] and pretend you did this step _ages_ ago!
1. Now run `ngrok http --bind-tls true 4000` and copy the `ngrok.io` URL from the "Forwarding" row of the dashboard now displaying in your Terminal
1. Paste that URL into the "Homepage URL" field in github's New App form
1. Paste it into the "Callback URL" field below that, and add `/auth/github/callback` to the end of it in that field
1. Check the box for "Request user authorization (OAuth) during installation"
1. (This doesn't pertain to OAuth, directly, but will come up for you later while you're building something else, so just) paste your `ngrok.io` URL into the "Webhook URL" field
1. run `mix phx.gen.secret | tee .scratchpaper | pbcopy` in your Terminal. This generates a strong, random 32-character long string, saves it to a file at the base of your project called `.scratchpaper`, and then immediately pushes it to your clipboard  (Linux users, you'll want to use `xclip -selection clipboard` [which you'll want to install { unless you're not running the X window system, then idk what to tell you ¯\\\_(ツ)_/¯ } from your package manager] in place of `pbcopy`)
1. Paste that secret into the "Webhook Secret" field on Github's New App page
1. Select the permissions you'll need in order to support your app's features beyond authorization
    1. Make sure you're subscribed to their webhook events
    1. And that you add that `webhook_secret` to your `config/dev.secret.exs` or `.envrc` (If you copied something else to your clipboard before getting to this step, running `pbcopy < .scratchpaper` will put it back there for you)!
  
Oof. That was a fun detour. But it was necessary!
{% enddetails %}

[^daemons]: If you're not a fan of opening a tunnel from the public internet into port `4000` (or `4001`) (or `whichever crazy port number you Docker kids use`) of your dev box, the other option you have here is to only test your integrations on `staging` and other remote environments. github.com just plain _cannot_ interpret `localhost` or `127.0.0.1` to mean **your** local machine. You may be able to configure your router to serve your dev website, if you're especially savvy, but those really are your really limited options for this particular quandary.

---

## This tutorial: The Code Parts

### Dependencies

You begin with the dependencies in your mixfile:

```elixir
# mix.exs

defp deps do
  # ...
  {:ueberauth, "~> 0.6.3"},
  {:ueberauth_github, "~> 0.8.0"}
  # ...
end
``` 


### <a name=router></a> Router

Then you define the pipelines and routes you'll use for authentication, and for authorization-restricted access.

```elixir
# lib/your_app_web/router.ex
  
import YourAppWeb.UserAuth, only [fetch_current_user: 2, require_signin: 2]

pipeline :browser do
  plug :accepts, ["html"]
  plug :fetch_session
  plug :fetch_live_flash
  plug :protect_from_forgery
  plug :put_secure_browser_headers
  plug :fetch_current_user
end

pipeline :authentication_required do
  plug :require_signin
end

scope "/", YourAppWeb do
  pipe_through :browser

  get "/register", AuthenticationController, :register
  get "/login", AuthenticationController, :login
  get "/logout", AuthenticationController, :logout      # not strictly RESTful, but also won't require your logout link to be a    
  delete "/logout", AuthenticationController, :logout

  scope "/auth" do
    get "/:provider", OAuthController, :request, as: :oauth
    get "/:provider/callback", OAuthController, :callback, as: :oauth
  end

  scope "/", do
    pipe_through :authentication_required

    get "/dashboard", DashboardController, :show
  end
end

# We'll cover this next week
scope "/hook", YourAppWeb.Hook do
  pipe_through :api

  post "/github", GithubController, :received, as: :github_webhook
end
```

Since you've decided to rely purely on OAuth for your registration and login workflows, instead of a `UserController` and `SessionController` for managing your users and logged in sessions, you're making an `AuthenticationController` for the front-facing register and login pages (as well as the logout functionality) and an `OAuthController` for handling the backend handoff to retrieve your users' credentials from various OAuth providers, as well as logging them in once everything's copacetic. You've also got an `:authentication_required` pipeline now, which allows you to softly kick users who aren't logged in, out of the parts of your app that they shouldn't access.

Now, obviously, we need to talk about the `YourApp.UserAuth` module you're importing the `:fetch_current_user` and `:require_signin` plugs from, but there's extra functionality in  `UserAuth` whose usages we haven't seen in the workflow quite yet, and since I want to avoid hopping into and out of each module's code multiple times as I describe how they all fit together, it makes more sense to go through the workflow layer by layer[^layers] instead of drilling down into deeper modules as soon as we encounter the first usage of a novel function.

[^layers]: breadth-first code analysis!

### Controllers, Views, and templates

So with that in mind, `AuthenticationController` is the next step on our journey!

```elixir
defmodule YourAppWeb.AuthenticationController do
  use YourAppWeb, :controller
  alias YourAppWeb.UserAuth
  
  def register(conn, _) do
    render(conn, "register.html")
  end

  def login(conn, _) do
    render(conn, "login.html")
  end
  
  def logout(conn, _params) do
    conn
     |> UserAuth.log_out
     |> redirect(to: Routes.root_path(conn, :index))
  end
end
```

The templates for `register.html` and `login.html` are almost identical. Of course, as you're using the [Slime template engine](https://github.com/slime-lang/slime), the differences are much easier to spot:

```slim
/- lib/your_app_web/templates/authentication/register.html.slime

h1 Sign Up

h3 Choose your repo host

.services
  = link to: app_installation_page(), class: "button github" do
      img.icon src=Routes.static_path(@conn, "/images/octoutline.svg") alt="Github Logo"
      ' Connect through Github

p Already registered? #{link "Log in instead", to: Routes.authentication_path(@conn, :login)}.

```

```slim
/- lib/your_app_web/templates/authentication/login.html.slime

h1 Log In

h3 using the host you signed up with

.services
  = link to: Routes.oauth_path(@conn, :request, :github), class: "button github" do
    img.icon src=Routes.static_path(@conn, "/images/octoutline.svg") alt="Github Logo"
    ' Github

p Not a user yet? #{link "Sign Up instead", to: Routes.authentication_path(@conn, :register)}.
```

Minor differences in copy aside, the thing to pay special attention to is **the URL your app uses to send users to Github's site**. The `login` page uses the normal `link to: Routes.some_derived_path(...), ... do` function that you'll use  regularly in your templates to allow users to reach various parts of your app. In this case, it's to the `oauth_path` for the `request` action for the provider `github`, which (you'll soon find) provides you literally **no** direct insight into how your users get from there to Github's site. In contrast, the `register` page uses `app_installation_page/0`, which you've defined as a helper within `YourAppWeb.AuthenticationView`:

```elixir
defmodule YourAppWeb.AuthenticationView do
  use YourAppWeb, :view

  @app_installation_page "https://github.com/apps/"
    <> Application.get_env(:your_app, :github)[:app_name]
    <> "/installations/new"

  def app_installation_page, do: @app_installation_page
end
```

{% collapsible A note on module attributes %}
This pattern may seem familiar to people coming from a background working with Ruby and Rails, where a declaration like `@app_installation_page` would signify that you were creating an instance variable. And in a few ways, [module attributes](https://elixir-lang.org/getting-started/module-attributes.html) can behave _somewhat_ similarly to instance variables... just, in a very "immutable functional programming" kind of way. Also, not at all.

The important thing to note in this usage is that ***module attributes are finalized at compile time,*** meaning the value of `@app_installation_page` will be inlined at its call sites during your app's build phase, and even if the result of `Application.get_env(:your_app, :github)[:app_name]` changes later on, the return of `app_installation_page/0` will stay the value that was set according to your environment config.

```elixir
# config/dev.exs

config :your_app, :github,
  app_name: "something"

# ---

# config/{runtime,releases}.exs

config :your_app, :github,
  app_name: "or_another"
```

```bash
$ iex -S mix

iex> Application.get_env(:your_app, :github)[:app_name]
"or_another"

iex> YourAppWeb.AuthenticationView.app_installation_page
"https://github.com/apps/something/installations/new"
```
{% endcollapsible %}


As indicated by the `/installations/new` portion of the URL, this link will take new users to Github's "Install Github App for the logged in Github User/Organization" page, which is the one that enables all of those permissions you figured out your app needed, whereas the `Routes.oauth_path/3` link before the app is installed will take your new users to Github's "Authorize _OAuth App_ for the logged in Github User". The difference is subtle in appearance, but it determines whether your app has the fine-grained permissions it needs to function properly (Github App), or the side-of-a-barn broad category permissions it needs in order to log the user in, and then... do literally nothing else involving an integration with GitHub (OAuth App).

Speaking of `OAuth`, that's exactly the `Controller` that  makes up the next part of our journey! And, uh... this is a bigger one.

```elixir
defmodule YourAppWeb.OAuthController do
  use YourAppWeb, :controller

  alias YourApp.Accounts
  alias Accounts.User
  alias YourAppWeb.UserAuth

  plug :set_unique_state when action == :request
  plug :verify_unique_state when action == :callback
  plug Ueberauth

  def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
    case Accounts.get_or_create_user_with_credentials(auth) do
      user = %User{} ->
        some_success_path = nil # you'll want to fill this in with
                                # something, likely from `Routes`
        conn
         |> UserAuth.log_in(user)
         |> redirect(to: some_success_path)
      %Ecto.Changeset{} ->
        conn
         |> put_flash(
              :error, 
              "Inserting the user and/or some of its associated #{
              }records failed. Check to make sure the schema #{
              }constraints and incoming data all conform."
            )
         |> redirect(to: Routes.authentication_path(conn, :register))
      nil ->
        conn
         |> put_flash(
              :error, 
              "Something went really wrong."
            )                   # and you'll definitely want to debug this.
         |> redirect(to: Routes.authentication_path(conn, :register))
    end
  end
  def callback(conn, %{"error" => error, "error_description" => message}) do
    require Logger
    Logger.error(%{"error" => error, "message" => message})
    conn
     |> put_flash(:error, "OAuth request failed in transit. This has been logged and will be looked into.")
     |> redirect(to: Routes.authentication_path(conn, :register))
  end
  
  defp set_unique_state(%{query_params: %{"state" => _}} = conn, _), do: conn
  defp set_unique_state(%{path_params: %{"provider" => provider}} = conn, _) do
    state = :crypto.strong_rand_bytes(24) |> Base.url_encode64()

    conn
     |> put_session("oauth_state", state)
     |> redirect(to: Routes.oauth_path(conn, :request, provider, state: state))
     |> halt
  end

  defp verify_unique_state(conn, _) do
    state = fetch_query_params(conn).query_params["state"]
    unique_state = get_session(conn, "oauth_state")

    conn
     |> delete_session("oauth_state")
     |> verify_unique_state(unique_state, state)
  end
  defp verify_unique_state(conn, state, state), do: conn
  defp verify_unique_state(conn, _expected, _tampered) do
    %{path_info: ["auth", provider | _]} = conn
    conn
     |> put_flash(
          :error, 
          "Something occurred while trying to authenticate with #{provider}. Please try again"
        )
     |> redirect(to: Routes.authentication_path(conn, :register))
     |> halt()
  end
end
```

The plugs `:set_unique_state` and `:verify_unique_state` are safeguards for preventing MitM attacks. MitM stands for _Malcolm in the Middle_, a sitcom which follows a kid genius growing up in a wholly dysfunctional family, and which aired on the Fox network from January 2000 until May 2006 to both critical and popular acclaim. It also stands for ***Meddler*** in the Middle, which is a case of internet attack where someone intercepts the data over your connection, and then either steals the sensitive details therein, sends malicious data back to you, or does both of these. These functions provide a safeguard. Details in this footnote ==> [^state-csrf]

[^state-csrf]: The `:set_unique_state` plug creates a random token that it safely adds to the <a href="#session-encryption">encrypted session</a> and will send within the `request` to the authentication provider — Github's server, in this case — and which the authentication provider will pass back to us so that we can verify it prior to doing the work of the `callback` action, in the `:verify_unique_state` plug. In that function, we retrieve the copy of the `state` we'd stored in the session, and compare it to the value we get back in the provider's query parameters to us. If there's a discrepancy, we redirect the client and display an error to them; if they match, we continue through to our `callback`. There's zero potential for an attacker to build rainbow tables against these tokens, so we can compare them using pattern-matching in the function signatures `def verify_unique_state(conn, state, state)` and `def verify_unique_state(conn, _expected, _tampered)`. To wit, we're telling the Erlang VM (and Elixir compiler), "This function expects to be called with either the same value passed in at two different spots, or with two different values (whose actual data we don't care about aside from the fact that they're not identical)." In security critical parts of your app, you will want to use a constant-time comparison technique, like 

#### In which Ueberauth plays gatekeeper in more ways than one

There's probably a burning question on your mind: "Where the h\*ck is `request` implemented?" First of all, don't cuss; and second, it turns out that `plug Ueberauth` handles it. However, as I am neither the author of Ueberauth, nor an eldritch sorcerer, I cannot explain the how, what, or why of any of that `plug` call's inner workings, try though I have, multiple times. For hours.[^ueberauth-magick]

[^ueberauth-magick]: If anyone reading this has a better understanding than I do of how Ueberauth conjures this action from the æther, I would love to know.

Let's move on; the `callback` action receives a `conn` that's already had either an `Ueberauth.Auth` or `Ueberauth.Failure` struct added to its `assigns` under either the `:ueberauth_auth` or `:ueberauth_failure` key, respectively[^ueberkeys]. The `Ueberauth.Auth` struct contains everything you need in order to create a User and their corresponding authorization tokens within `Accounts.get_or_create_user_with_credentials/1`.

[^ueberkeys]: I hope it's at least a little obvious where these are coming from.

Explaining how to create Ecto records out of data received from a controller is outside of the scope of this tutorial.[^next-tutorial] The way I've structured my app, `get_or_create_user_with_credentials/1` persists records to three distinct tables in the database: one for the user, one for their OAuth profile, and one for their OAuth tokens (by default, Github uses a combination of short-lived access tokens and longer-lived [but one-time use] refresh tokens).

[^next-tutorial]: I already have my topic planned for the next part of this series, but if there's enough interest in it, I can write about creating Ecto records after that!

The call to `Accounts.get_or_create_user_with_credentials/1` occurs in a `case` clause so that we can delegate what to do based on its success or failure. In this three-split path, receiving a `nil` value from it means something went wrong that we had no idea could happen, an `%Ecto.Changeset{}`  struct implies that there was an issue trying to persist one or more of the records to the database, and being able to match `user = %User{}` in the topmost case means we successfully came back out of the function with the user and their credentials all persisted to the database. In the former two cases, we present an error to the user (which should be ourselves until we've worked most of the kinks out of this function). In the latter case, we invoke `UserAuth.log_in/2` and then redirect to some path the user would logically be directed to next.

At this point, your user has successfully logged in through OAuth, and has a persistent session. The confetti cannons are overjoyed 🎉  

#### You're not done quite yet though

Of course, those all just compose the outer boundary of `YourApp`; the parts your users will directly engage for access to the things they're trying to do. That is to say, your _user_ can theoretically go through the complete sign-up/sign-in workflow, but _you_ still have a little more business logic to implement deeper down in `YourApp`.[^login-dot-biz] <br id="going-auth-the-rails" />

[^login-dot-biz]: Yes, I'm counting user authorization and session retention as important business logic. Some web apps incorporate neither of these things, yet still provide a revenue stream to their developers. Your app authenticates users and persists a session for them as part of what it helps them do; if that isn't the case, have you read this far into the tutorial strictly for entertainment value? <br id=going-auth-the-rails />

## 🎵 Going auth ~~the rails~~ phoenix on a ~~crazy~~ cookie train 🎵

Let's look at `YourAppWeb.UserAuth`next: <br id=userauth />

### `UserAuth`

```elixir
defmodule YourAppWeb.UserAuth do
  import Plug.Conn
  alias Phoenix.Controller
  alias YourAppWeb.Router.Helpers, as: Routes
  alias YourApp.Accounts
  alias Accounts.{User, OauthLogin}
  
  @doc """
  Retrieves `conn.assigns.current_user`, setting it first if necessary.
  """
  def current_user(%{assigns: %{current_user: user?}}), do: user?
  def current_user(conn), do: conn |> fetch_current_user |> current_user

  @doc """
  Store a user's OAuthLogin.id alongside an expiration time within
  the encrypted session cookie.
  """
  @spec log_in(Plug.Conn.t, %User{oauth_logins: [OauthLogin.t]}) :: Plug.Conn.t
  def log_in(conn, %User{oauth_logins: [login]} = _user) do
    conn
     |> put_session("login_token", login.id)
     |> configure_session(renew: true)
  end
  def log_in(conn, _non_user), do: conn

  @doc """
  Clears the session.
  """
  @spec log_out(Plug.Conn.t) :: Plug.Conn.t
  def log_out(conn), do: configure_session(conn, drop: true)

  @doc """
  Redirects to the login page if `conn.assigns[:current_user]` is `nil`.

  Runs `fetch_current_user` if `:current_user` is unassigned.
  """
  def require_signin(conn, opts \\ [])
  def require_signin(%{assigns: %{current_user: %User{}}} = conn, _),
    do: conn
  def require_signin(%{assigns: %{current_user: nil}} = conn, _),
    do: conn
     |> Controller.put_flash(:error, "You need to be logged in to use that")
     |> Controller.redirect(to: Routes.authentication_path(conn, :login))
     |> halt
  def require_signin(conn, _) when not is_map_key(conn.assigns, :current_user),
    do: conn
     |> fetch_current_user
     |> require_signin

  @doc """
  Loads the user record from the token stored in the session and adds it to the
  `conn` assigns.

  If a user record can't be loaded from the token, say, because
  the token is expired or no corresponding record could be found,
  then the session is dropped and the client will be asked to log in
  again the next time they access a path requiring authorization.

  This plug is memoized; re-running it during a request will
  retrieve the previously-returned value, and not further
  alter the `conn` in any way. To re-run the fetch, delete
  the `:current_user` key from the `conn.assigns` map.
  """
  def fetch_current_user(conn, _opts), do: fetch_current_user(conn)
  def fetch_current_user(%{assigns: %{current_user: _}} = conn), do: conn
  def fetch_current_user(conn) do
    login_token = get_session(conn)["login_token"]
    {conn, user?} = case load_user_from_session_token(login_token) do
      {:ok, user} ->
        {conn, user}
      {:no_session_token, nil} ->
        {conn, nil}
      {:token_refresh_denied, user} ->
        message = "There was a problem refreshing your provider's access token, repos may not display the latest data"
        {Controller.put_flash(conn, :error, message), user}
      {_error, nil} ->
        {log_out(conn), nil}
    end

    assign(conn, :current_user, user?)
  end

  defp load_user_from_session_token(login_id) when is_binary(login_id) do
    with %User{} = user <- Accounts.get_user_with_login(login_id),
         {:ok, user} <- maybe_refresh_user_tokens(user) do
      {:ok, user}
    else
      nil -> {:user_not_found, nil}
      {:token_refresh_denied, _user} = result -> result
    end
  end
  defp load_user_from_session_token(nil), do: {:no_session_token, nil}
  defp load_user_from_session_token(_), do: {:user_not_found, nil}

  @hour 60 * 60
  defp maybe_refresh_user_tokens(%User{oauth_tokens: [bearer_token]} = user) do
    hour_from_now = DateTime.add(DateTime.utc_now(), @hour)
    comparison = DateTime.compare(bearer_token.expires_at, hour_from_now)
    with comp when comp in [:lt, :eq] <- comparison,
         {:ok, tokens} <- Accounts.refresh_bearer_token(bearer_token) do
      {:ok, %{ user | oauth_tokens: tokens }}
    else
      :gt = _comparison -> {:ok, user}
      {_error, _old_token} -> {:token_refresh_denied, user}
    end
  end
end
```

I've taken care to describe how each public function works within its `@doc` tag, so enough of this should be straightforward, but a quick rundown of the functions shouldn't hurt anybody.[^confused] <br id=current_user-1 />

[^confused]: If anyone is left still feeling confused after reading this (entire post), don't hesitate to ask questions in the comments. Clarifying the problem points will only make this a better tutorial, and above everything else (besides keeping a roof over my head. roofs are above literally _everything_ in software), I want this tutorial series to leave as few loose ends as possible.

####  `.current_user/1`
As with most lazy television, we draw in the audience by starting our episode in the middle of the action: `UserAuth.current_user` will retrieve the already-assigned `:current_user` from the connection's `assigns`, or it will invoke `fetch_current_user/1` to populate the key and then retrieve its value. But why does it call `fetch_current_user/1` at all, and how does `fetch_current_user/1` work?

_\*record scratch\* \*freeze frame\*_[^freeze-frame] Yeah, that's (a function to retrieve an app's database representation of) me. You're probably wondering, how'd it get into this situation? Well...

[^freeze-frame]: This is the longest it has taken in any media, ever, to get to the _\*record scratch\* \*freeze frame\*_ flashback.

_\*The Who's "Baba O'Riley" seems to begin playing deep within the very fabric of reality.\*_ <br id=log_in-2 />

#### `.log_in/2`

_\*record scratch\*_ THERE'S NO TIME TO EXPLAIN, we have to flashback to the [inciting incident](https://tvtropes.org/pmwiki/pmwiki.php/Main/IncitingIncident), `UserAuth.log_in/2`! This function is called with a `conn` and a `YourApp.Accounts.User` struct, which the function head pattern-matches on when its `oauth_logins` relation contains exactly one `YourApp.Accounts.OauthLogin` struct.[^exactly-one]  This login's `id` will be our session token.[^id-in-token] Finally, `log_in/2` is going to `configure_session(conn, renew: true)` before passing the `conn` back to the function that called `log_in`. The `:renew` option makes Plug generate a new session ID to give back to the client within our server's response, to protect from [session fixation attacks](https://owasp.org/www-community/attacks/Session_fixation). <br id=log_out-2 />

[^exactly-one]: You may have noticed that the `User` schema is using an `oauth_logins` association, plural, yet our pattern match is expecting exactly one login in that list. This is for two reasons. First, our app anticipates supporting various different OAuth providers, and even with several different OAuth providers, one user will still be one `User`, which will be an important architecture decision when users want to interact with several different OAuth provider APIs at the same time. Second, although we will eventually have multiple logins per user, right now we only support Github, which only provides one login per user, and so if we found any more than one during any particular query, it'd be a pretty big clue that something wasn't _quite_ right with our database.

[^id-in-token]: Normally, it's dangerous to keep a user identifier in a cookie, because cookies are sent to the client in every request, and all of the JavaScript on the page can read the cookie string. We can be confident that nobody will be able to steal the user's login ID thanks to the minimal amount of configuration Phoenix and `Plug.Session` need in order provide strong encryption for our app's session data.

#### `.log_out/2`

`log_out/2` is the most straightforward of all the functions in `UserAuth`. It prevents the session cookie from being sent back to the client at all on this request. The next action the client takes (which, in the case of our `AuthenticationController.logout/2` function, is to load the app's root `index` page), the app will treat them as a visitor until they log back in as a `User`.  And with that exposition out of the way... <br id=require_signin-2 />

#### `.require_signin/2`

We still haven't learned anything about how `fetch_current_user/1` works, but we've got bigger problems to deal with, because `require_signin/2` is creating some serious thematic tension in its presentation of a conflicting philosophy to the one set forward by our established narrative (_gasp!_)! Like some kind of twisted[^twisted] version of `current_user/2` from that mirror dimension in _Star Trek_ where everyone has facial hair that makes them hotter but also, *evil*, `require_signin/2` has many similarities to the latter function, but instead of always letting the `conn` continue through the Plug pipeline, in a very evil way, it *doesn't do that* necessarily *in every circumstance* (_**bigger gasp!**_)! 

[^twisted]: At least... "twisted" in the context of lazy, reductive tropes we endure  constantly in our TV shows.

THERE'S NO TIME TO EXPLAIN, but let me try anyway. `current_user/2` has a "live and let live" approach to things: If it can't find `assigns[:current_user]`, it'll make a call to fetch it, and it's okay with whatever the fetch finds, whether that's an actual `%User{}` struct, or a `nil` value. If `current_user/2` sees a `nil`, it's just like "Hey, client, I know how it is. We all feel a little `nil` from time to time, but it doesn't mean we're bad protocols." Then it lights a joint and listens to Grateful Dead for the rest of the request while the rest of the pipeline is at work. Meanwhile, `require_signin/2`'s philosophy feels closer to the one embraced by the fictional nation setting of the Bleakpunk computer game [_Papers, Please_](https://papersplea.se/). But its workflow is only applied to the parts of your app that go through `YourAppWeb.Router`'s `:authentication_required` pipeline, so rather than being an unwitting stooge of totalitarianism who's just trying to keep his family fed, `require_signin/2` is really more like a hyper-vigilant, omnipresent, standing-slightly-too-close-to-you bouncer. It makes sure every client's name is "on the list" every time an action has them come through its pipeline. When a client even so much as refreshes the page they're on, `require_signin/2` swings by and double-checks the list to make sure they're still on it, gently heaving them back out to the lobby the instant it can't verify their listworthiness, telling "VIPs only, buddy", before it goes back to powerwalking the perimeter of the club, incessantly checking the IDs of every user either in or approaching the VIP room.

Actually, `require_signin/2`'s not so bad. Imagine if `current_user/2` were the bouncer in front of your VIP room; nobody would ever be turned away! Sure, your clients may be a little annoyed if their session ever expires and they have to sign in again, in a circumstance they find "unexpected", but hey, that session expiration is for their benefit. Like I mentioned when we went over `configure_session(conn, renew: true)`; doing what you can to keep attackers from impersonating your users is crucial. If you didn't have safeguards like renewing the session ID and setting expiration times, and an imposter ever managed to get a copy of a user's token, they could impersonate that user for as long as they wanted, and you wouldn't be able to stop them without taking drastic measures, assuming you found out there was an imposter at all. And if I've learned one thing from watching weird TikTok clips of people doing things that are allegedly relevant to the team-based, route-out-imposters video game sensation of 2020, _Among Us!_[^among], it's that imposters can outsmart your teammates and destroy your spaceship. And they will. Every. Single. Match.

[^among]: it's that I do not understand the kids today and am officially an Old.

<figure align="center">
  <a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dwRPXz9Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8eipo36bc83y36sph6j.gif" class="article-body-image-wrapper"><img width=450 src="https://res.cloudinary.com/practicaldev/image/fetch/s--dwRPXz9Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8eipo36bc83y36sph6j.gif" alt="An animation of astronauts from the computer game _Among Us_. One is walking down a corridor, past a room where he witnesses an imposter space alien, disguised as another astronaut, slip into one of the air vents. Panicked, the astronaut runs back to go hit the emergency meeting button, but the imposter has beaten the astronaut there, and they press the button first. In the meeting, the other astronauts vote their belief that the astronaut is actually the imposter, resulting in them being thrown out of an airlock into space" loading="lazy" /></a>
  <figcaption><small>Imposters wreak havoc on things, both in outer space, and in <em>cyber</em>-space.</small></figcaption>
</figure>

That honestly doesn't have much of anything to do with user authentication workflows or stale session attacks. I just thought putting a colorful, funny gif here would break the monotony of this giant wall of text, a bit.

OKAY, now that I've covered `current_user/2`, `log_in/2`, log_out/2, require_signin/2, and teased it almost as long as George R.R. Martin teased Daenerys' dragons, we finally arrive at… **The moment we flashed back from!** <br id=fetch_current_user-2 />

#### `.fetch_current_user/2`

_\*record scratch\*_ THERE'S NO TIME TO EXPLAIN; I need to finish explaining all of this module's functions! `fetch_current_user/2` is at the crux of your Phoenix app's session persistence capabilities. It gets the value stored in the session map under the key `"login_token"` and conditionally operates on `conn` based on the result of `load_user_from_session_token/1`.  If the result is `{:ok, user}`, there doesn't have to be any further processing on either the `conn` or the user in this function, aside from adding the user to `conn.assigns`. Conversely, with `{:no_session_token, nil}`, we aren't getting any `user` to assign to the conn, but we know that since it's because no session token was loaded to begin with, we don't have to log the user out or display any kind of error message about what happened. `{:token_refresh_denied, user}` means that while the integration didn't allow `YourApp` to refresh the user's access token and the user will be unable to perform any actions against Github's API, they can still interact with the data cached with `YourApp`, although `YourApp` will show a notification that their data may not be in sync with what Github has. Any other sort of response from `load_user_from_session_token/1` essentially means that the user couldn't or shouldn't be loaded; right now, `fetch_current_user/2` regards those cases as instances where the user should be logged out from the session, but you can change that if your use cases call for different behavior.

Now, a funny thing about teaching: It can help reinforce your memory regarding what you know, but it can _also_ lead you to make new discoveries and learn even more. This tutorial was originally written with a version of `load_user_from_session_token/1` that involved verifying that the `login_token` hadn't expired. This was because neither the series of blog posts, nor the section of _Programming Phoenix ≥ 1.4_ on session persistence, mentioned any sort of built-in mechanism that the server might use to expire session cookies. Indeed, a random dive into `Plug.Session`'s documentation while writing this tutorial lead me to discover a `max_age` option, which would have been helpful in the time between writing the comparison logic for both expiring the login token and refreshing the bearer token, and realizing that the API was being called to refresh the bearer token for every action while a user was logged in. Mine is apparently the first Phoenix tutorial to recognize the sublime importance of having your users verify who they are every once in a while. It's fine. The information being helpful to me is why I now pass it on to you 💖

The version of `load_user_from_session_token/1` presented here is pretty straightforward. I don't particularly feel like I need to walk you through this one.

`maybe_refresh_user_tokens/1` matches on the user struct, like `log_in/2`, but it's interested in the user's `:oauth_tokens` as opposed to their `:oauth_logins`. The single entry here is going to be the bearer token — the token they'll use to interact with Github's API. They also have a refresh token, which `Accounts.refresh_bearer_token/1` retrieves and uses to request a new value for the bearer token from the Github integration.[^always-load-refresh?] Of course, it only needs to do this if the access token is going to expire soon. If it is, it calls to refresh the token, updates the `User` with the new value, and then sends the `{:ok, updated_user}` envelope back to its caller with the new token on a successful refresh. If the access token isn't expiring soon, it passes the user back to the caller. unchanged, in the `{:ok, user}` envelope. Any error results in it sending `{:token_refresh_denied, user}` back, so that the user can still use `YourApp`'s integral features.

[^always-load-refresh?]: While it feels natural to just load your refresh token as part of `fetch_current_user/2`, there really isn't a compelling reason to. Its sole purpose is to update the bearer token, which only needs to happen about once every 8 hours, and loading it within such a common part of your app is unnecessary runaround for the database in the best case, and exposing it to an unnecessary attack surface in the worst. That's why all of the queries in the `Accounts` context concerning a user's workflow-relevant data are scoped specifically to just the user's bearer token(s), with the exceptions of the aforementioned `refresh_bearer_token/1` and functions related to removing a user's records when they request for their account to be deleted, or when they revoke access to one of their OAuth providers.

Although it's only 110 lines of code, it's easy to marvel at the little behemoth  `UserAuth` is within `YourApp`'s workflow. But even if it was daunting to consider when you began, you've learned that Phoenix and Plug lay down enough of the infrastructure necessary to assemble it relying on the help (or hindrance) of a larger third-party package (especially one that takes a token standard designed to allow web apps to confidently send messages to other web apps and tries to squeeze it into the already-solved problem space of session management).[^jwt-the-good-parts]

[^jwt-the-good-parts]: If you are curious about how JWTs should **typically** be used, keep an eye out for the next tutorial in this series. We may have built out letting `YourApp`'s users log in through OAuth with their Github profile today, but next is the fun part: Integrating with Github's developer API 😏

We aren't out of the woods _quite_ yet, though. `YourApp`'s endpoint and its config still have a few things to go over. <br id=yourappweb-endpoint />

### `YourAppWeb.Endpoint`

```elixir
defmodule YourAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :your_app
  @endpoint_config Application.get_env(:your_app, YourAppWeb.Endpoint)

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  @session_options [
    store: :cookie,
    key: "_your_app_key",
    max_age: @endpoint_config[:session_max_age] || 604800,   
    signing_salt: "f+tfIyAdTnKXiTMc",
    encryption_salt: "4/zKvGZ0eXRikpgT+INdEtLw"
  ]

  socket "/socket", YourAppWeb.UserSocket, websocket: true, longpoll: false

  socket "/live", Phoenix.LiveView.Socket, websocket: [
    connect_info: [session: @session_options]
  ]

  # …
  
  plug Plug.MethodOverride
  plug Plug.Head
  plug Plug.Session, @session_options
  plug YourAppWeb.Router
end
```

You might be worried about storing these salts in a file that's checked-in to version control. I'm not a security expert, but the common philosophy in that regard among security experts: [Don't panic](https://security.stackexchange.com/questions/17421/how-to-store-salt#answer-17435). It is safe to have a salt be a known value. The important thing to keep from leaking is your `secret_key_base`. Even if an attacker knows your salts, they still won't be able to forge your signature on a cookie, or decrypt the contents of one that they've gotten hold of without `YourApp`'s `secret_key_base`. Though, if the paranoia overwhelms you, you can always keep your secret values in your shell environment and then retrieve them in your config files, or put them in files that aren't checked-in to source control.[^ignore-secret-configs]

```gitignore
# <your_app_root>/.gitignore
# …
/config/secret/

# or possibly
*.secret.* # extension in the middle of a chain
*.secret   # extension at the end of the chain 
# …
```

[^ignore-secret-configs]: The benefit of going this route is that if you're deploying using Elixir releases, your app won't need those secret files once it's through its build (compile) phase. The values will be baked into `YourApp`'s optimized BEAM bytecode, and you can configure it to never print them to standard out or a log file. Depending on your security hygiene, this can be much safer than setting environment variables on your server and loading them inside of `config/runtime.exs`.

### <a name=config-exs></a> `config/*.exs`

Speaking of config files, that brings us to the last piece of this workflow. You'll need to configure `Ueberauth` by informing it to use the Github strategy when Github is the OAuth provider like so:

```elixir
# config/config.exs

config :ueberauth, Ueberauth,
  providers: [
    github: {
      Ueberauth.Strategy.Github, [
        allow_private_emails: true
      ]
    }
  ]
```

And you'll additionally need to provide Ueberauth's Github strategy with the `Client ID` Github lists for your app, as well as a `Client secret`, which you can generate for your app by clicking the button marked "Generate a new client secret" within the header of the Client Secrets section of `YourApp`'s developer settings. It'll look something like this:

<figure align="center">
<a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cjVcuLVJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/miaituodogymwh933saz.png" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cjVcuLVJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/miaituodogymwh933saz.png" alt="An example of a defunct client secret from github.com. The screenshot is annotated with the message &quot;lol the app this key is for is already deleted&quot;" loading="lazy"></a>
  <figcaption>(kids, don't try this at home) (by which I mean, don't post screenshots of your client secrets)</figcaption>
 </figure>

```elixir
# config/{dev,staging,prod,releases,runtime}.exs

config :ueberauth, Ueberauth.Strategy.Github.OAuth,
  client_id: "add in secrets",
  client_secret: "add in secrets"
```

And you'll want to keep at least the `client_secret` a secret, using whichever secret-storing strategy sublimely suits your style.

--- <br id=you-made-it />

## You made it![^maybe-me-tho?]

[^maybe-me-tho?]: unless you ripped all of the code from this tutorial and renamed a few modules and/or functions; then _I_ made it

...FWAHHH, hello, End of the Post!  *24* footnotes, 578 lines, 7660 words, and 47823 characters![^wc] But we got through it together, all of the code examples are contiguous, working pieces of code (most of them... at least...), nobody had to dig through multiple other blog posts in order to get all of the parts put together, and even _I_ learned a few things in the process. I'd say that just about the only thing that'd do better to prove this was a successful tutorial, would be for you to ~~buy me a Ko-fi~~ help keep my phone line connected!

[^wc]: footnote and `wc` counts are representative of the statistics of this post up to (and *excluding*) the link to this footnote

No, seriously. If you appreciated/enjoyed/learned something from/shamelessly ripped code wholesale from this tutorial, and you want to keep seeing more tutorials like them, it's infinitely easier for me to write them when I can pay my electric bill, my internet bill, and my rent! Donating to my [Ko-Fi](https://ko-fi.com/thepeoplesbourgeois36330) or directly to me on [Square Cash](https://cash.app/$bwomp) not only helps me to be able to keep this blog going, it also helps me do things like make good on the payment schedule I told my phone provider I'd adhere to for paying off my overdue fees! 

[![A link to my Ko-Fi account, styled like a button which reads, "Help me keep my phone line!"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv2l0iwxarh1f5iuhy6v.png)](https://ko-fi.com/thepeoplesbourgeois36330)

Also, let me know down in the comments what aspects of these tutorials I could improve in future posts; better reference materials for languages and frameworks lead to better tools built in those languages and frameworks.

Meanwhile, keep a lookout for the next tutorial in this series: _Taming Webhooks, while failing to tame the single mutable component you might ever encounter in all of Elixir_