7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

初見3分(本当は12分)でAuth0を使ったOIDCログインを実装するまで

Last updated at Posted at 2021-08-07

完全に初心者の趣味です。勤務先や所属する団体の活動とは関係ありません。

tl;dr

  • OpenID Connect(OIDC) でログインする、Single Page App(SPA) もどきをNode.jsで作りました。
  • 作ったSPAもどきで、Auth0を利用してみました。サインアップから、Auth0を使ったログイン完了まで、初見で12分で終わりました。
  • 一応利用規約も簡単に目を通した。
  • 一番迷ったのは無料サインアップまでのルート。
  • 作ったSPAもどきの解説は後半です。
  • Google IDでログインするための設定も最後に。

試してみる

Google IDログインですが、Glitchにもサンプルをアップしました。

まずは実際に動かしてみる

Node.jsがlocalhostで動く環境が前提です。(localhostじゃないときはlocalhostって書いてある部分を適当に変えてください)

Auth0のサインアップ

Google検索で「Auth0」と入れて、広告の下のリンクをクリックします。
Google-auth0.png

広告のリンクをクリックすると、サインアップページにたどり着きません。

右上の「無料トライアル」をクリックして、GitHubでも、Googleでも、好きなアカウントでサインアップします。
そういえば自分の名前すら入力していない気がする。

Auth0-trial.png

サインアップが終わったら、すぐダッシュボードが表示されます。
右上の、「+Create Application」をクリックします。

Auth0-createApp.png

適当にアプリ名を考えてから、今回は「Regular Web Applications」を選択します。
多分、ここは何を選んでも大丈夫な気がします。
Auth0-createApp2.png

次の画面で、「Quick Start」が出ますが、そこは使わず、その隣の「Settings」タブを開き、
「Client ID」、「Client Secret」を確認します。(後でコピペします。)
Auth0-BasicInfo.png

下にスクロールしていって、「Application URIs」の中の、「Allowed Callback URLs」に、 http://localhost:3000/ と入力します。
Auth0-AppURI.png

さらに下にスクロールしていって、「Advanced Settings」を開きます。
一番右の「Endpoints」をクリックします。
この中の、「OAuth Authorization URL」と、「OAuth Token URL」を、後でコピペします。
Auth0-endpoints.png

Node.js コードのダウンロードと設定

下記のレポジトリをチェックアウトします。面倒なら、ここからソースのZIPを直接ダウンロードします。

ファイルを解凍したら、ターミナル、もしくはコマンドプロンプトでnpm install を実行します。

処理を待っている間に、.env.exampleをエディタで開き、先ほどAuth0の設定ページで見つけた、
「OAuth Authorization URL」、「OAuth Token URL」、「Client ID」、「Client Secret」を、それぞれコピペしたら、.envとして保存します。

Auth0-editor.png

起動

node app.js を実行します。

実際にログインしてみる

ブラウザで、 http://localhost:3000 にアクセスします。

上から順に、(1)、(2)のボタンを押していくと、Auth0のログインページに行きます。
Auth0-demo1.png

「Sign up」から、eメールとパスワードを入力してログインすることもできますし、デフォルトでGoogleログインが使えるように設定されています。
Auth0-demo2.png

「Accept」を押すと、元のページに戻ってきます。
Auth0-demo3.png

(3)を押すと、Node.js経由で、Auth0のサーバから、ユーザ情報(いわゆるIDトークン)を取得して、その中身をデコードして表示します。
Auth0-demo5.png

ここまで実測12分で終わってしまいました。。始めたときは、タイムアタックするつもりなんてなかったので、終わってから時計を見てびっくりしました。最初のサインアップページに迷ったり、利用規約を読んだりしなければ、本当に3分で終わったんじゃないかと思います。

SPAもどきの作り方

作った理由

いわゆる普通のWebサイトで、GoogleやFacebook,Twitterなどを使ったソーシャルログインを実現する例はよく見るのですが、ネイティブアプリでのソーシャルログインは、各サービスがSDKを提供しているため、仕組みがよくわからず、ブラックボックスになってしまっている気がしました。
なので、ネイティブアプリを作るのはちょっと難易度高いですが、静的なHTML+JavaScriptで、サーバとはREST APIで通信して、ネイティブアプリっぽく振る舞う、SPAもどきを作ることで、仕組みを理解しようと思いました。

参考にしたサイト

日本におけるOpenID Connectの重鎮のお二方のブログを参考にアーキテクチャを考えました。いつもお世話になっております。

アーキテクチャ

REST APIっぽく作りたかったので、簡単にできるNode.js + Express構成を使います。
SPA部分は、JavaScriptも含めて1つのHTMLファイルにしています。
(作り始めたときは、一部動的な部分があるかなと、ejsを使いましたが、結果的に完全に静的なページになっています。)

OpenID ConnectでSPAを作る場合、インプリシットフローやハイブリッドフローで、SPAが直接IDトークンを取得するパターンがよく紹介されている訳ですが、IDトークンにもそれなりに個人情報が含まれる場合があることと、実装をシンプルにするために、サーバ、SPA間はセッションIDで管理することにしました。

ログインのフローはこんな感じです。(1)から(3)は、それぞれ実際のSPAもどきを使うときに押すボタンに対応しています。

Auth0-OIDC-flow.png

通常Webサイトであれば、ユーザのログイン状態はセッションIDをCookieに保存することで管理します。
ですが、ネイティブアプリを想定して、セッションIDはAuthorizationヘッダーに入れることにしました。ブラウザ内ではlocalStorageに保存します。

こういう設計で本当に問題ないのかは、識者のアドバイスを頂戴したいところです。私は初心者なので。

各処理の解説

(1) ログインリクエスト

SPAは静的なHTMLで、サイトを開いた時点ではセッションIDは付与していません。
Ajaxで、サーバに対して、セッションIDの払いだしと、IDP(ID Provider:認証サーバ、ここではAuth0)にログインするためのAuthorizationエンドポイントのURLや、パラメータを取得します。
※ ここでAuthorizationエンドポイントのURLを取得しているので、例えばIDPをAuth0からGoogleに切り替える場合も、サーバ側の設定を切り替えるだけで、SPA(HTML/JavaScript)側の修正は不要になります。
ついでに、SPA側でも、CSRFから防御するため、stateという乱数を生成しておきます。

サーバ側では、ここでセッションIDを払い出し、ログインのためのPKCEコードを生成して、各種パラメータとともにSPAに返却します。
PKCEについての説明はこちら

サーバから返ってきたセッションIDは、後で使うのでlocalStorageに保存しておきます。
今回作ったSPAでは、デモ用にセッションIDを表示していますが、通常は隠しておくべきです。

(2) ログインページに接続

(1)でサーバから返ってきた AuthorizationエンドポイントのURLとパラメータに、SPAで生成したStateをくっつけて、アクセスします。
ネイティブアプリであれば、外部ブラウザやCustomTab, SFSafariViewControllerやASWebAuthenticationSessionを使いますが、今回はブラウザなので単純にlocation.hrefで移動することにします。

遷移先で無事ログインが成功すると、元のページに、クエリパラメータ「code」と「state」が付属して返ってきます。
ネイティブアプリであれば、Android App LinksやUniversalLinksを使ってアプリに処理を戻すことが推奨されます。詳細はBCP212/RFC8252を参照してください。
今回作ったSPAでは、エラー処理は省略しています。

(3) 認可コードとセッションID送信

SPAに認可コード(code )とstateが返ってきたので、まず、stateが事前に保存しておいた値と一致するかを検証します。もし一致しなかったら、悪意ある人からの攻撃の可能性があります。
※ といっても、PKCEを使っている限り、その先の処理が成功しないのでユーザ情報の流出などの可能性は低いですが、stateを使わずに、codeが戻ってきたら自動でサーバに送信してログインを試みるような処理をしていると、勝手にログアウトされちゃったりしかねないので、検証処理を入れておいた方がいいと思います。

state が一致しているようなら、サーバに、認可コードを送信します。その際、(1)で保存しておいたセッションIDを、Authorizationヘッダーに付与します。

今回作ったSPAでは、デモのために、(3)の処理を手動で行うようにしていますが、本来であれば、認可コードがIDPから戻ってきてから、Stateを比較し、サーバに送る一連の処理は自動で行うべきです。

認可コードを受信したサーバは、セッションIDと紐付けて保存しておいたPKCEコードと、クライアントID/クライアントシークレット、認可コードをIDP(Auth0)に対して送信します。
すると、IDPから、IDトークンが返ってきます。

IDトークンをデコードすると、ユーザID(sub値)がわかるので、サーバの中では、sub値をキーにしてユーザを管理します。
サーバ内で、セッションIDと、sub値を紐付けてデータベースに保存したら、ログイン完了です。

SPAでは、デモのため、IDトークンの中身を返却しています。

(4) セッションIDの利用

今までの処理で、セッションIDとユーザが紐付いたので、セッションIDが安全に管理されている限り、セッションIDの送り主=そのユーザであるということを前提にサービスを提供します。
今回作ったSPAでは、セッションIDを送ることで、ログイン時にサーバに保存したユーザ情報を取得することができるようにしてみました。
試しにブラウザをリロードして、(4)のボタンを押してみてください。(3)を実行したときと同じ情報が返ってくることが確認できます。

Google IDでログインしてみる。

今度は、Auth0を使わずに、直接Google IDでログインできるようにしてみます。

Google ログインの設定

Google IDでOIDCログインを行うための設定は、Auth0に比べると、少し大変です。
まず、Google Cloud Platformで新しいプロジェクトを作ります。

そして、「認証情報」を開き、「+認証情報を作成」から、「OAuthクライアントID」を選択します。

GCP-createOAuth.png

すると、先に同意画面を設定しろと言われてしまいました。言われたとおり、「同意画面を設定」をクリックします。
GCP-consenterror.png

User Typeは、企業ユーザなどでない限り、「外部」しか選択できません。
GCP-consent.png

アプリ名など、必要な情報を適宜入力します。
GCP-consent-appinfo.png

スコープを選択するよう求められますので、最低限```openid``だけは指定します。
次の画面で、テストユーザを登録するよう求められますが、登録しなくてもログインだけはできるようです。
GCP-scope.png

仕切り直して、「認証情報」を開き、「+認証情報を作成」から、「OAuthクライアントID」を選択します。

アプリケーションの種類は「ウェブ アプリケーション」としました。
承認済みのリダイレクトURIに、http://localhost:3000 を追加します。
GCP-createClientID.png

「作成」を押すと、「クライアントID」と「クライアント シークレット」が表示されます。
これを、先ほどダウンロードしてきた、app.jsにコピペします。
GCP-clientsecret.png

ドキュメントを見ても、Googleの「OAuth Authorization URL」、「OAuth Token URL」の設定はどこなのか、サンプル電文以外で見つけることができませんでした。おそらく、OIDC仕様に準拠して、OpenID Configurationを見ろということなんだと思います。(GitHubのソースコードは、元からGoogleの設定にしてありますので、Auth0をまだ試してみていない場合は、そのままで大丈夫です。)

GCP-editor.png

app.js
const OIDC_AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth"
const OIDC_TOKEN_ENDPOINT = "https://www.googleapis.com/oauth2/v4/token"

Google IDでログインしてみる。

設定が終わったら、node app.jsをCtrl+Cで終了してから再起動して、新しいタブを開いてhttp://localhost:3000に再度アクセスして見てください。
(1),(2),(3)と順番にボタンを押していけば、今度はGoogleのIDトークンの中身が取得できました。

GCP-userinfo.png

ちなみに、「新しいタブを開いて」とお願いした理由は、冒頭で取得したAuth0のセッションが残っていれば、
そっち側のセッションもちゃんと有効で、Auth0で取得したユーザ情報を取得することができます。
GoogleとAuth0で、iss値が異なるのがわかると思います。

Auth0-userinfo.png

セッションDBの中身

今回作ったSPAはデモなので、1つのテーブルですべてのデータを管理しています。PKCEコードとか、ログイン終わったら消してもいいんですけどね。
あと、DBの中を直接見て気づいたんですが、Auth0のsub値には、|(縦棒) が含まれてますね。いまどきいないと思いますが、SQLを生でいじるような人は要注意ですね。

% sqlite3 oidc_db.sqlite ".schema"              
CREATE TABLE session (session_id TEXT UNIQUE NOT NULL, pkce_code TEXT, subject_id TEXT, user_info TEXT, active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT (DATETIME('now', 'localtime')));

% sqlite3 oidc_db.sqlite "select * from session"
C2WRrorCttmBOuY9g6FXbw|jp(中略)Gv|auth0|610e48bed3fd1200687bb379|{(中略)"iss":"https://oidc-for-spa.jp.auth0.com/",(中略)}|0|2021-08-07 18:38:58
6DvaFpPv5rVUioruwnBXxw|QU(中略)6L|113688351101139242200|{"iss":"https://accounts.google.com",(中略)}|1|2021-08-07 21:49:40

##感想

OAuth / OpenID Connect という標準に準拠して各社がサービスを提供してくれているおかげで、最低限の設定変更で、各社のIDaaSを使って、Webやアプリでのログインが実現できることがわかりました。標準化バンザイ。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?