Elixir × Überauth で Phoenix に OAuth2認証 を実装する

# 概要

適当なサービスにログインしようとした時、以下のようなものを見たことがありませんか？

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/f0e492f0-edf6-44b2-9e32-3dfe8b2222ed.png" width="50%" />

Github アカウントでconppassにログインしてみる、こういった認証を **OAuth 2.0** と呼ぶそうです。

今回はこの OAuth2認証を Phoenix に実装してみたいと思います。

公開するリポジトリには記事内の手順に合わせてブランチ分けしておりますので、段階的に実装手順を沿っていけると思います。
ぜひご活用ください。

# 環境

以下の環境で動作確認しております。

* macOS Sequoia 16.5
* elixir 1.18.2-otp-27
* erlang 27.3.2

# リポジトリ

記事中のコードは以下に配置しています。

https://github.com/BlackTanuTanuNinjas/tutorial_ueberauth

# 本題

ElixirPhoenix に OAuth2認証を実装するために **Überauth** というライブラリを使いたいと思います。

https://github.com/ueberauth/ueberauth

このライブラリ一本で、Gootle や Twitter などのプロバイダーごとに設定を行えば、複数のサイトの OAuth2認証 を実装することができます。

今回は Github の OAuth2認証 に絞って実装を行います。

# 手順

今回は次のコマンドで phxプロジェクト を生成しているものとします。

```bash
mix phx.server tutorial_ueberauth
```

次の手順で OAuth2認証 の実装を行います。

1. [依存を追加](#依存を追加)
2. [認証ファイルを作成する](#認証ファイルを作成する)
3. [設定ファイルの変更](#設定ファイルの変更)
3. [gen.authの追加](#gen.authの追加)
5. [Controllerの追加](#controllerの追加)
6. [リンクの追加](#リンクの追加)
7. [実行](#実行)

## 依存を追加

Überauth で Github の OAuth2認証 を実装するには、下記の依存を追加します。

```elixir
  defp deps do
    [
      # 省略
      {:ueberauth, "~> 0.10"},
      {:ueberauth_github, "~> 0.8"}
    ]
  end
```

## 認証ファイルを作成する

Github に限らず OAuth2認証 を行うには認証情報が必要です。

Github の認証情報は以下のリンクで **OAuth Apps** を作成することで取得できます。

https://github.com/settings/developers

アクセスしたら、`New oauth app` を押して、作成に入りましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/33417c84-3931-44dd-9bf1-a41b2d03bfc1.png" width="50%" />

ここで次の内容を入力しましょう。

* `Application name`
    * 好きな名前をつけてください（用途がわかる名前など）
* `Homepage URL`
    * ホームページのURLを指定します
    * `https://localhost:4000`
* `Application description`
    * 説明文を記入（オプション）
* `Authorization callback URL`
    * ここの [コールバック](#Controller の追加) が呼べるURLを指定する
    * `http://localhost:4000/auth/github/callback`

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/60e0f5dc-edb6-40ed-81af-2253a14cc353.png" width="50%" />

OAuthApps を作成できたら、**Client ID** と **Client secrets** が生成されました。
この二つの認証情報を使用して、OAuth2認証を行います。

:exclamation:**注意**： Client secrets は再度表示することができないので必ずメモを取りましょう。

## 設定ファイルの変更

認証情報を取得できたので、`config.exs` の変更に入ります。

`config.exs` に下記の内容を追加してください。

```elixir:config/config.exs
config :ueberauth, Ueberauth,
  providers: [
    github: {Ueberauth.Strategy.Github, []}
  ]
```

`runtime.exs` に下記の内容を追加してください。
`runtime.exs` は Phoenix に実行されます。

```elixir:config/runtime.exs
config :ueberauth, Ueberauth.Strategy.Github.OAuth,
  client_id: System.get_env("GITHUB_CLIENT_ID"),
  client_secret: System.get_env("GITHUB_CLIENT_SECRET")
```

そして先ほどの認証情報を `.env` ファイルを作成し、書き込みます。

```:.env
GITHUB_CLIENT_ID=<ClientID>
GITHUB_CLIENT_SECRET=<Client secrets>
```

## gen.authの追加

`gen.auth` を実行して、いつもの認証を追加しましょう。

```bash
mix phx.gen.auth Accounts User users    
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/db7d8163-c40f-49a9-a620-4b8da40796a2.png" width="50%" />


## Controller の追加

OAuth2 の設定が済んでいるので、実際に認証を行う Controller を作成しましょう。

まず `router.ex` に下記を追加してください。

```elixir:lib/tutorial_ueberauth_web/router.ex
  # Überauth のスコープを追加
  scope "/auth", TutorialUeberauthWeb do
    pipe_through :browser

    get "/:provider", AuthController, :request
    get "/:provider/callback", AuthController, :callback
    post "/:provider/callback", AuthController, :callback
    delete "/logout", AuthController, :delete
  end
```

次の `auth_controller.ex` を追加してください。

```elixir
defmodule TutorialUeberauthWeb.AuthController do
  @moduledoc """
  Auth controller responsible for handling Ueberauth responses
  """

  use TutorialUeberauthWeb, :controller

  plug Ueberauth

  alias Ueberauth.Strategy.Helpers
  alias UeberauthExample.UserFromAuth

  def request(conn, _params) do
    render(conn, "request.html", callback_url: Helpers.callback_url(conn))
  end

  def delete(conn, _params) do
    conn
    |> put_flash(:info, "You have been logged out!")
    |> clear_session()
    |> redirect(to: "/")
  end

  def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do
    conn
    |> put_flash(:error, "Failed to authenticate.")
    |> redirect(to: "/")
  end

  def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
    conn
    |> put_flash(:info, "Successfully authenticated.")
    |> put_session(:current_user, auth.uid)
    |> configure_session(renew: true)
    |> redirect(to: ~p"/")
  end
end
```

これは OAuth2認証 のプロバイダーで共通となる認証を行うためのページへアクセスするための Controller になります。

先ほど Github の[設定](#認証ファイルを作成する)にて、下記をホームページとして設定していました。

```
http://localhost:4000
```

そして `auth_controller.ex` を実行すると次のリンクで Github の OAuth2認証 が行われるようになります。

```
http://localhost:4000/auth/github
```

なので、上のリンクへリダイレクトするタグを作成することで、Githubアカウントでログインできるようになりました。

下記を参照

https://github.com/ueberauth/ueberauth_example/blob/master/lib/ueberauth_example_web/controllers/auth_controller.ex

## リンクの追加

先ほど `gen.auth` にてログインページが生成されていると思いますので、そちらのログインページに Github での認証のリンクを追加しましょう。

```diff_elixir:tutorial_ueberauth_web.user_login_live
<.link href={~p"/auth/github"} class="w-full ">Githubでログイン／登録</.link> 
```

## 実行

ここまでの作業を終えて、やっと Github の OAuth2認証 ができるようになりました。

早速動作を確認しましょう。
`source` を実行して `.env` 読み込んでから Phoenixを起動しましょう。

```bash
source .env
mix phx.server
```

ログインページに飛んで、先ほど追加したリンクを押しましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/a735d113-ff43-4c25-ab2b-3229236def4b.png" width=50%" />

押してみると、初回だけ次の表示が出てきます。`Authorize` を押しましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/f02f24e0-59aa-484f-b39c-d04c1be637e8.png" width="50%" />

Github での OAuth2認証 の実装完了です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/29057368-5324-4d47-b5ab-2bef6773e144.png" width="50%" />

# まとめ

ここまでで、Github のOAuth2 認証が実装できました。
設定を色々追加すると別のプロバイダーのものでも認証ができるようになるかと思います。
詳しくは下記を参照してください。

https://hexdocs.pm/ueberauth/readme.html#configuring-providers

またログインしてみるとわかるのですが、このままではログインしても `current_user` が `nil` のままなので本当に認証をしたというだけに終わってしまっています。

なので本来は、別途ユーザーを作成するといった処理の追加が必要になると思います。

最後に要点をまとめて締めたいと思います。

Überauth で OAuth2認証 を実装するには以下の手順を踏みます。

1. Überauth の依存追加
2. プロバイダーの認証情報を作成する(`.env`)
3. Überauth の設定を追加する(`config.exs`、`runtime.exs`)
4. `router.ex`にスコープ追加
5. Controller を追加
6. 環境変数を読み込ませて実行

実践で使っていくには `gen_auth` との組み合わせの方法がネックなので、存じておられる方は教えていただけると幸いです。

# おまけ

## `%{ueberauth_auth: auth}` の中身を確認してみた

認証成功時の `ueberauth_auth` の値を `inspect` して確認してみました。
色々省略してます。

```elixir
%Ueberauth.Auth{
  uid: 12345678, # ← <ここにuid>
  provider: :github,
  info: %Ueberauth.Auth.Info{
    name: "<ここにGitHub表示名>",
    nickname: "<ここにGitHubユーザー名>",
    image: "https://avatars.githubusercontent.com/u/<ここにUID>?v=4",
    urls: %{
      html_url: "https://github.com/<ここにGitHubユーザー名>"
    }
  },
  credentials: %Ueberauth.Auth.Credentials{
    token: "<ここにアクセストークン（マスク済み）>",
    token_type: "Bearer"
  },
  # ...
}
```