How to externalize user authentication within Phoenix?

authentication
phoenix

#1

Can someone with current experience suggest a path on how to get started externalizing user authentication within Phoenix? I would like to give my users the ability to register/sign-in using Google, Facebook, etc.

Further, if I integrate with enough external providers, I assume that means I could (potentially?) forego entirely the responsibility of storing/managing users passwords. Is that a reasonable assumption?

There is a lot of information about this topic, but most of it seems dated… and the Elixir/Phoenix world moves quickly. I’d like to avoid starting on a dead-end path :).

Thanks in advance!


#2

Did You try ueberauth?


#3

You might check out Auth0. I haven’t used it personally, but I’ve seen others use it with success especially when bootstrapping a project.

They also wrote a blog post on using it with Elixir (and uberauth):


#4

Do you still want to register the users? I wrote CoherenceAssent to handle multi-provider login. While it’s built to use Coherence, I’ve been using it for just Github OAuth login in an admin interface where I just store the user information in the session. The controller looks like this:

defmodule MyAdminApp.AuthController do
  use MyAdminApp, :controller

  @organization "my-organization"

  @spec new(Conn.t, map) :: Conn.t
  def new(conn, _params) do
    config = Keyword.put(config(), :redirect_uri, callback_url(conn))
    {:ok, %{conn: conn, url: url}} = CoherenceAssent.Strategy.Github.authorize_url(conn, config)

    redirect(conn, external: url)
  end

  @spec callback(Conn.t, map) :: Conn.t
  def callback(conn, params) do
    params = Map.merge(%{"redirect_uri" => callback_url(conn)}, params)

    conn
    |> CoherenceAssent.Strategy.Github.callback(config(), params)
    |> verify_organization()
    |> login_or_redirect()
  end

  defp login_or_redirect({:ok, %{conn: conn, user: user}}) do
    conn
    |> put_session(:current_admin_user, user)
    |> redirect(to: index_path(conn, :index))
  end
  defp login_or_redirect({:error, %{error: error}}) do
    raise error
  end

  defp config do
    config = Application.get_env(:my_admin_app, :admin_github_provider) || raise "Need to set :admin_github_provider first!"
    Keyword.put(config, :authorization_params, [scope: "read:user,user:email,read:org"])
  end

  defp verify_organization({:error, error}), do: {:error, error}
  defp verify_organization({:ok, %{conn: conn, client: client, user: user}}) do
    case OAuth2.Client.get(client, "/user/orgs") do
      {:ok, %OAuth2.Response{body: orgs}} ->
        case Enum.find_value(orgs, &(&1["login"] == @organization)) do
          true -> {:ok, %{conn: conn, user: user}}
          _    -> {:error, %{error: "#{user["nickname"]} is not a member of #{@organization}"}}
        end

      {:error, error} ->
        {:error, %{conn: conn, error: error}}
    end
  end

  defp callback_url(conn) do
    auth_url(conn, :callback)
  end
end

And the plug looks like this:

defmodule MyAdminApp.Plug.RequireLogin do
  @moduledoc false

  import Plug.Conn
  import Phoenix.Controller, only: [redirect: 2]
  alias MyAdminApp.Router.Helpers

  @behaviour Plug

  @dialyzer [
    {:nowarn_function, call: 2},
    {:nowarn_function, init: 1},
  ]

  @spec init(Keyword.t) :: [tuple]
  def init(options) do
    %{option: options}
  end

  @spec call(Plug.Conn.t, any) :: Plug.Conn.t
  def call(conn, _opts) do
    user = get_session(conn, :current_admin_user)

    if user do
      conn
      |> assign(:current_admin_user, user)
    else
      conn
      |> redirect(to: Helpers.auth_path(conn, :new))
      |> halt
    end
  end

end

Routes:

# ...
  pipeline :protected do
    plug NomadRentalAdmin.Plug.RequireLogin
  end

  scope "/", MyAdminApp do
    pipe_through [:browser]

    get "/auth", AuthController, :new
    get "/auth/callback", AuthController, :callback
  end
# ...

It’s a very basic setup (since we’re just using it to log in to an internal admin interface with our Github organization), but might be something like this you’re looking for?

If you want to register the users still then I recommend checking out the readme for CoherenceAssent. It takes a minute, and then you can remove the ability to change the password on user admin page.


#5

Great work!
That is what I’m looking for. I’m using Phauxth instead of Coherence but after what I saw on your own library Pow, I will definitely give it a try.

Thank you for sharing this. ^^


#6

Thanks! I hope it works for you.

PowAssent has been improved to not be dependent on Plug, so if you use it without any user registration like in the example above, the API is a little different. But if you use Pow for user registration then there’s nothing you have to think about :smile: