Auth0を使ってSSOの流れを確認してみた


## はじめに
この記事は[OPENLOGI Advent Calendar 2022](https://qiita.com/advent-calendar/2022/openlogi)の9日目の記事です。

業務と全く関係ないのですが、今回は個人的に気になっていたSSOの具体的な流れについて手元で環境を作り処理を追っていきたいと思います。
[Auth0](https://auth0.com/jp)や[SSO](https://auth0.com/blog/jp-sso-login-key-benefits-and-implementation/)、またその他利用しているツール類の説明は、詳しく解説されてる記事がありますので割愛させていただきます。

## 概要
SSOの分かりやすいフロー図が[Auth0 blog](https://auth0.com/blog/jp-sso-login-key-benefits-and-implementation/)にありますので、Auth0を使ったSSOの１〜１３までの流れを確認します。
![typical-sso.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/db1c0bd4-eae8-aa90-30b3-eabf39a32746.png)


実際に利用する際は意識する必要はない項目もありそうですが、今回はざっと見ていければと思います。

## 確認準備
### 1. ローカル環境作成
SSOの確認なのでまずは２環境作っていきます。

作成は以下のコマンドをport番号違いで2回叩くだけです。
```terminal
$ php -S localhost:8888
PHP 8.1.11 Development Server (http://localhost:8888) started
```

### 2. 外部からローカル環境へアクセスできるようにする
[ngrok](https://ngrok.com/)を利用します。
service1と2は先ほど立ち上げたローカル環境をポート番号違いで紐づけてます。

```terminal
$ ngrok start service1 service2
ngrok                                                                                        (Ctrl+C to quit)

Try our new native Go library: https://github.com/ngrok/ngrok-go

Session Status                online
Account                       terumichi1209@gmail.com (Plan: Free)
Version                       3.1.0
Region                        United States (us)
Latency                       -
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://6fef-60-61-127-42.ngrok.io -> http://localhost:8888
Forwarding                    https://c78d-60-61-127-42.ngrok.io -> http://localhost:8889
```

これで外部からアクセスできる２つのローカル環境が作成できました。

### 3. Auth0へ生成したドメインを反映する
[Auth0管理画面](https://manage.auth0.com/dashboard)にて、ご自身の環境でアプリを以下のように２つ作成します。
![スクリーンショット 2022-12-03 17.08.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/dc716e9d-e975-9989-cb8b-979ccd138d43.png)

各アプリには生成したURLを設定します。
![スクリーンショット 2022-12-03 17.13.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/988a2e0c-041f-9b7e-a6d4-4c2b6c4c150c.png)

データベースを利用せず、ソーシャルログインだけの場合はこれでAuth0側は準備完了です。
![スクリーンショット 2022-12-03 17.18.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/55a68c04-91db-625b-5640-c9f177ed3757.png)

### 4. Auth0のアプリ情報を設定する
サービスごとにディレクトリを切っただけのアプリを用意します。
![スクリーンショット 2022-12-03 17.28.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/11f28bfa-b23e-2c38-02ca-fe0652989b59.png)

`index.php` は[Auth0クイックスタート](https://auth0.com/docs/quickstart/webapp/php#configure-auth0)のサンプルコードの寄せ集めになります。
ドメインやパス、Auth0のアプリ情報を変えて、２つの `index.php` に設定します。

```php
<?php
use Steampixel\Route;

const ROUTE_URL_INDEX = 'https://f272-60-61-127-42.ngrok.io/service1';
const ROUTE_URL_LOGIN = ROUTE_URL_INDEX . '/login';
const ROUTE_URL_CALLBACK = ROUTE_URL_INDEX . '/callback';
const ROUTE_URL_LOGOUT = ROUTE_URL_INDEX . '/logout';

require '../vendor/autoload.php';

$auth0 = new \Auth0\SDK\Auth0([
    'domain' => 'Auth0で作成したアプリのdomain',
    'clientId' => 'Auth0で作成したアプリのclientId',
    'clientSecret' => 'Auth0で作成したアプリのclientSecret',
    'cookieSecret' => 'test',
]);

Route::add('/service1', function() use ($auth0) {
    $session = $auth0->getCredentials();
    if ($session === null) {
        echo '<p>Please <a href="' . ROUTE_URL_LOGIN . '">log in</a>.</p>';
        return;
    }

    echo '<pre>';
    print_r($session->user);
    echo '</pre>';

    echo '<p>You can now <a href="' . ROUTE_URL_LOGOUT . '">log out</a>.</p>';
});

Route::add('/service1/login', function() use ($auth0) {
    $auth0->clear();
    header("Location: " . $auth0->login(ROUTE_URL_CALLBACK));
});

Route::add('/service1/callback', function() use ($auth0) {
    $auth0->exchange(ROUTE_URL_CALLBACK);
    header("Location: " . ROUTE_URL_INDEX);
});

Route::add('/service1/logout', function() use ($auth0) {
    header("Location: " . $auth0->logout(ROUTE_URL_INDEX));
});

Route::run('/');
```

以上で設定は完了ですので実際に見ていきましょう。

## 結果
それでは、1から順を追って確認します。
![typical-sso.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/db1c0bd4-eae8-aa90-30b3-eabf39a32746.png)
### 1. Browses to
サービス１にアクセスします。

![スクリーンショット 2022-12-03 14.47.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/18f33f57-3dab-cfe5-424f-ad75b062605f.png)

### 2. Redirects to
ログインボタンを押すとユニバーサルログイン画面（Auth Server）にリダイレクトします。
![スクリーンショット 2022-12-03 14.51.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/e016ccfd-0e32-af7e-f18a-53a7959b48dc.png)

### 3. Enter user logs in, or cookie is avaliable
ここではまだログインした記録となるCookie情報はないため、ユーザ情報をユニバーサルログイン画面上で入力（またはソーシャルログインなど）します。

### 4. Store Cookie
入力したユーザ情報がAuth0が保持するユーザと一致（もしくは新規作成に成功）した場合、Auth0側の認証はOKなのでその旨をCookieに保持します。
![スクリーンショット 2022-12-03 15.22.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/167bc79e-ed87-97fa-942c-0aed1a1132f6.png)

### 5. Sends token and redirects
この後の認証に必要な `code（token）`や`state`が付与されてAuth0アプリで設定したcallback先にリダイレクトします。
![スクリーンショット 2022-12-03 15.07.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/7003d589-a2c6-91df-6852-f9b436ea377a.png)

### 6. Uses Token to Authenticate
codeを利用して認証します。
ライブラリ内で良しなにやってくれてますね。
https://github.com/auth0/auth0-PHP/blob/main/src/Auth0.php#L300

付与されてるパラメータを利用してaccess_tokenやid_token(jwt)を生成します。
認証後に取得できるトークンは復号することで、どういったデータ持ってるかの参考になります。
![スクリーンショット 2022-12-03 15.28.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/db598dd5-2465-40d7-fa13-73bff3020b2a.png)

### 7. Stores domain1 Cookie
認証を終えるとCookie情報が追加されます。
![スクリーンショット 2022-12-03 15.44.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/f300a207-1e11-f16e-0fe3-7fa0495f0d72.png)

アプリ側では以上で無事ログインできたので、ユーザ情報が出力できました。
![スクリーンショット 2022-12-03 16.04.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/85dd26a5-cc85-0607-dd95-5cd7c683b3da.png)

先ほど確認したトークン内容がそのまま出てることが分かります。

### 8. Browsers to
次にドメインを変えてサービス２にアクセスします。

![スクリーンショット 2022-12-03 16.08.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/17f91caf-769d-d493-b16c-3ca44b77d044.png)

### 9. Redirects to
ログインボタンを押して、Auth Serverにリダイレクトします。

### 10. Enter user logs in, or cookie is avaliable
今度はサービス１のフローで登録したCookieを保持しているため、どのユーザかAuth0上では特定できてます。
そのため、入力を省略して次のステップへ進みます。

### 11. Sends token and redirects
同じように`code（token）`や`state`が付与されてサービス２へリダイレクトします。
![スクリーンショット 2022-12-03 16.22.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/562c6f68-27fa-227f-8054-e24e80963e22.png)

### 12. Uses Token to Authenticate
省略

### 13. Stores domain1 Cookie
サービス１と同じようにサービス２のドメインでCookieが作成されていることが分かります。
![スクリーンショット 2022-12-03 16.26.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/b8183955-33b5-bde4-3a6f-73c711a67549.png)

結果も同様に出てることが確認できました。
![スクリーンショット 2022-12-03 16.28.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72433/492bedc8-5494-56fb-3fdf-ac647ed1153c.png)


## 最後に
いかがでしたでしょうか？

図で見るだけより実際に動かした方が理解できて（特にトークン生成周り）、記事を書いて良かったと思います。
ライブラリ内深掘りするともう少しありますが、冒頭の図の流れは追えたので今回はこの辺りで終わります。

今回の実験用に作成したコードはGithubへ上げてますので、興味がある方は手元の環境でもお試しいただけます。
https://github.com/terumichi1209/auth0_sso_sample/