Auth0でSSOをどうやって実現しているか実験してみた

## はじめに

Auth0のUniversal LoginでSSOが実現できると公式ドキュメントに書いてありましたが、Application側のSession管理の方法や複数アプリケーションでログイン/ログアウトをした場合の動作など、詳細な仕組みが書かれていなかったので、QuickStartを使って簡単に実験してみました。

今回試したのは、Regular Web ApplicationとSingle Web Applicationです。
NativeとMachine to Machine Application (Shell Script)については試していません。

また、今回取り扱うのはUniversal Loginなので、埋め込みログイン方式のSSOについては触れません。
埋め込みログインがCross Originでの認証になるので、セキュリティーリスクがあったり、ネイティブでは実現不可能ということが書かれていましたが、なぜ不可能かまではわからなかったので、これは別記事で触れたいと思います。

## TL;DR

- REGULAR WEB APPLICATIONの場合、Session Cookieでログイン状態を管理
- SINGLE PAGE APPLICATIONの場合、Cookieの認証済みフラグでログイン状態を管理
- サイトAでログアウトしても、Sessionが生きてる限り、サイトBではログアウトされない

## SSOとは

SSOはSingle-Sign-Onの略字です。
よく企業システムで複数のアプリケーションを利用する際に用いられる事が多いということもあり、SSOという言葉は知っているけど、何を指しているのか僕の中で曖昧だったので、改めて書いてみます。

僕の中の認識では、「一つのIDで複数のアプリケーションで認証ができる仕組み」です。
大体文字通りだと思いますが、一応例を出してみよおうと思います。

例えば、ある3つのアプリケーションApp1, App2, App3がある場合を考えてみます。

もし、SSOを使わない場合は、3つのアプリケーションで登録処理を行う必要があります。
そのため、アプリケーションごとにID/PWが必要になり、管理が煩雑になりやすいです。

一方で、SSOを使う場合、登録するのは一つのアプリケーションだけで、残りのアプリケーションは連携の許可だけで済みます。
そのため、複数アプリケーションがあったとしてもID/PWは一つだけになり、ユーザ目線から見ると管理が容易になります。

最近だと、GoogleログインやFacebookログインなどのソーシャルログインが流行っているのでイメージしやすいと思います。もちろんソーシャルログインもSSOの一つです。

SSOには色々な実装方法があるらしいのですが、それについてはまだ調べきれていないので、ここでは飛ばしたいと思います。

## Auth0でのSSO

Auth0について簡単に説明すると、認証に特化したマネージドサービスです。いわゆるIDaaSってやつですね。
https://auth0.com/jp/

Auth0には、Universal Loginと呼ばれるログイン方式が存在します。
大まかな仕組みは下の埋め込みログインとの比較図を見てもらうと想像がつくと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/baddb34e-a3f8-ec40-4cdc-2a0ac8bbb2be.png)
https://auth0.com/docs/guides/login/universal-vs-embedded

Universal Login(左図)はAuth0がHostingするサーバーで認証を行い、各アプリケーションへリダイレクトする方式をとっています。なので、ログインしたときのセッションはAuth0ドメインのSession Cookieとして保管されます。

一方で、埋め込みログイン(右図)の方は各アプリケーション内で認証を行うため、ログインセッションは各アプリケーションで持つことになります。
埋め込みログインでSSOを行う場合は、Third Paty Cookieに認証状態が保管されるというふうに[ドキュメントには書かれていました](https://auth0.com/docs/cross-origin-authentication#what-is-cross-origin-authentication-)が、実際に試してはいないので、詳細な動きは不明です。

ここまでは大体想像がつくと思うのですが、Central Loginの場合、アプリケーション側のログイン状態の管理をどのように行っているのかドキュメントからはわからなかったので、実際にアプリケーションを立てて試してみました。
検証用に立てたアプリケーションは以下の2つです。どちらもAuth0のQuickStartを用いました。

1. Laravel製の一般的なWeb Application
2. React製のSingle Page Application

## 1. Laravel製の一般的なWeb Application

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/9447d679-859d-e46c-e659-a364438f0f1c.png)

[このQuickStart](https://auth0.com/docs/quickstart/webapp/laravel/01-login)の通りに進めていきます。
Auth0のQuickStartの良いところは、ログインしていれば、Auth0のクライアントIDやエンドポイントなどの情報が埋め込まれたサンプルアプリケーションをzipファイルでダウンロードすることができることです。

### Auth0側の設定

Auth0へログインし、ダッシュボードへ移動します。
サイドバーの`Applications`を選択し、`CREATE APPLICATION`をクリック。
下記図のように、Application名を入れて、「Regular Web Applications」を選択し、`CREATE`をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/96485589-7a56-6ec4-d7d6-8358af4d5ccf.png)

Applicationの作成が完了したら、以下図のように `Allowed ***`となっている項目に `http://localhost:3000`を追加します。
これを設定しないと、うまくリダイレクトされなくなります。

また、`Allowed Callback URLs`は、`http://localhost:3000/callback`に設定します。
(Callback URLはLaravelサンプル内の.envに書いてあるで、もし異なっていたら.envの設定を書きます。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/7e74bb0c-005a-09af-bf72-66014cdc9db4.png)

### Laravel編

具体的な手順とDockerでの開発環境構築を書きます。

1. 「LOG IN & DOWNLOAD SAMPLE」をクリックし、Applicationを選択
2. 「DOWNLOAD」をクリックし、サンプルをダウンロード
2. 適当な位置に配置
3. 解凍&実行

```
unzip laravel-01-login.zip
cd 00-starter-seed
sh exec.sh
```

これだけです。

`exec.sh`を実行すると、docker上にlaravelのWebサーバコンテナを起動してくれます。
`composer install`や`artisan`周りの初期化もしてくれるので楽ちんですね。

サーバー起動後は `http://localhost:3000` に接続するとWebページを見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/0232c1c4-2a3f-7049-ddc0-3ef4000fb84b.png)

右上のサイドバーにLoginボタンがあるのでクリックしてみます。
すると、以下のような `{tenantID}.auth0.com`のページに飛ばされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/0bee58a2-e003-8347-bbe4-e61b909646a0.png)

このページでログインしてみます。
すると、図のような感じでUser情報をDumpすることができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/acc0c3e0-6292-6a61-6182-37de8cb93da6.png)


このユーザ情報がどこに保管されているかというと、LaravelのSessionになります。
Chromeであれば、Developer ToolsのApplicationタブのCookieの中にある、`laravel_session`がSession Cookieになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/b8cb4334-fe68-7b4b-e5b7-81ef0978ca5f.png)

Session情報の中身はサーバー内で保管されています。
サンプルアプリケーションでは、`SESSION_DRIVER=file`になっているので、コンテナに入って確認してみます。

```
docker exec -it {container_id} sh
cat storage/framework/sessions/{文字羅列}
```

中には、トークンとユーザの情報が含まれていました。

- auth0__access_token
- auth0__id_token
- auth0__user

どうやら、Universal Loginでセントラルログインをしたとしてもセッション情報はLaravel側で管理されるようです。

## 2. React製のSingle Page Application

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/e08f001f-3a3c-bdb9-1ab7-f67cd51f5337.png)

Laravel同様QuickStartを利用します。Reactの場合[このQuickStart]を利用します。
設定の流れは大体Laravelと同じなので割愛します。必ずLaravelとは別のAuth0 Applicationを作成してください。

注意点として、複数サービスでのログインしたときの挙動をテストしたいので、PortはLaravelのものとは別にします (適当に5000番とかにしてください)。

React側でPortを変更する場合は、exec.shを以下のように書き換えてください。

```bash
#!/usr/bin/env bash
docker build -t auth0-react-01-login .
docker run --init -p 5000:3000 -it -e auth0-react-01-login
```

ReactのSPA環境が出来上がると、以下のような画面が表示されると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/15ad4163-3dfb-0b4c-b2ad-96df404f9cc8.png)

早速ログインしてみます。Laravelのサンプルで使用したブラウザでログインしてみてください。
すると、以下のような画面が表示されます。
英語ですが、 あなたのprofileとemailにアクセスする許可をアプリケーションに与えても良いか？というふうに問われています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/e8b90d56-0ba9-c866-a8e1-4b9dbd91d164.png)

お気づきかもしれませんが、このとき別アプリケーションにも関わらず、ログイン画面は表示されませんでした。
この連携の許可だけ行うと、Reactのサンプルアプリケーションの方でもログインされたことになります。
連携の許可後、 http://localhost:5000/profile にアクセスしてみるとユーザ情報を見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/329f2e1b-b636-9386-4864-465d3a73dc8b.png)

さて、このユーザー情報はどこで管理されているのでしょうか?
先程と同じようにCookieを見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120358/d5b94012-84ba-2250-3eef-b9c04b6e45ed.png)

すると、 `auth0.is_authenticated` というKeyでログイン状態が管理されていました。
実際にこのCookieを削除したりfalseにしたりしてみましたが、Reactアプリケーション上でのみログアウトされました。

## ログアウトの挙動はどうなるか

LaravelとReactの2つのアプリケーションが建てられたところで、片方のアプリケーションでログアウトした場合の挙動がどうなるかを試してみます。

まず、2つのアプリケーションにログインします。
次に、Reactアプリの方でログアウトしてください。
その後、Laravelアプリの画面へ行き、更新ボタンを押してください。

どのような挙動になったでしょうか？

恐らく、Laravelのアプリケーション側ではログアウトされずにログインセッションが保たれたままになっているかと思います。

つまり、Auth0でUniversal LoginでSSOをした場合は、アプリケーション側のセッション情報はアプリケーション側で管理するようになっているということがわかります。

## まとめ

Auth0のUniversal LoginでSSOをどのように実現しているのか実験してみました。

結果として、セントラルログインをした場合は、Auth0ドメイン上にSession Cookieが保管され、一度ログインした後は、ログイン画面を見ずともログイン状態になることが確認できました。

しかし、ログアウトした場合はログアウトしたアプリケーションのみログアウトされ、ログアウトしていないアプリケーションのセッションは継続されることがわかりました。
つまり、SSOをしていたとしてもログインセッションはアプリケーション側で管理する必要があるということです。

仮に、一つのアプリケーションでログアウトしたら、他のアプリケーションでもログアウト状態にしたいユースケースでは、認証が必要なページで、大元のAuth0のセッションが切れていないか確認する処理が必要になりそうです。

Auth0はかなりカバーされている範囲も広いし、IDaaSの中では最強クラスだと思っているので、これからどんどん使い倒していくこうと思います。現場からは異常です。