今回は「Googleを例にOpenID Connectの実処理フロー」についてまとめます。
今回も、OpenID Connect すべてをカバーするのではなく、一番サポート率が高く使われるであろうユースケース「BasicOP(response_type: code
)」についてのみを対象にまとめます。
概要
今回は Google の OpenID Connect / OAuth 2.0 を使ってみることで、具体的な OpenID Connect の実装イメージを持てるようにしていきます。
OpenID Connect の大まかな処理フローは以下のようなイメージです。
- 利用アカウント(OpenID)選択
- ログイン & 許可
- リダイレクト with 認可コード
- トークンリクエスト with 認可コード → アクセストークン、IDトークン
- 個人情報リクエスト with アクセストークン → 個人情報
概要レベルの話は別記事(OpenID Connect の 概要)を書いていますのでそちらも参照してみてください。
ただ…上記の図に示されているのは「OpenIDを利用する場合の処理フロー」のみです。
実際は「事前にOpenID提供事業者に対して登録処理」が必要になります。
ここで事前の登録処理も含めた全体を対象にまとめていきます。
- 事前準備(OpenID提供側サーバー設定)
- クライアント実装(OpenID利用側処理フロー)
事前準備
OpenID や OAuth を利用する場合、あらかじめ OpenID 提供サービス側に対して登録作業が必要になります。
Googleの OpenID Connect ないし OAuth を利用したい場合、 Google Cloud Console (GCP) の 「APIとサービス」 においてあらかじめ設定をしておきます。
GCPを利用するにはあらかじめプロジェクトの作成や支払い設定が必要になりますが…ここでは割愛します(=プロジェクト作成と支払い設定は終わっている前提)。
事前設定を行うのは以下の2か所になります。
「Googleでログイン」を選んだあと表示される「○○がGoogleアカウントへのアクセスをリクエストしています」画面の設定です。
-
「APIとサービス」の左メニューから「OAuth同意画面」を選択
-
「外部」を選択して「作成」
-
以下の設定をして「保存」
- アプリケーション名
- 必須。エンドユーザーがGoogleのログイン画面へ遷移してきたとき表示される。
- アプリケーションのロゴ
- 任意。
- サポートメール
- いずれかを選択。
- Google API のスコープ
- ログインさせるだけならデフォルトのまま(
email
, profile
, openid
)でOK。
- 承認済みドメイン
- テストだけなら空欄でOK。本番環境あるなら本番のドメインを指定。
- その他リンク
- 任意。
エンドユーザーがGoogleのアカウント情報を提供することを許可したあと、「どういった方法でOpenIDを受け渡しするのか」と「どこへリダイレクトさせるか」の設定です。
-
「APIとサービス」の左メニューから [認証情報] を選択
-
上部メニュー [認証情報を作成]-[OAuthクライアントID] を選択
-
以下の設定をして「保存」
- アプリケーションの種類
- 今回は
response_type: core
にするので「ウェブアプリケーション」。
- 名前
- 適当な名前に変更します。
- 承認済みのJavaScript生成元
- 基本は空欄。JavaScript実装しない(= XSSを気にしなくてよいシステムにする)ので。
- 承認済みのリダイレクトURI
- テストようであれば http://localhost:8080/ などを入力。
ここに設定する値はこのあと出てくる redirect_uri とプロトコル(
http
or https
)や /
含めて一致している必要があるので要注意。
-
完了画面に表示される「クライアントID」「クライアントシークレット」を保存
完了画面で「クライアントID」と「クライアントシークレット」をメモし忘れていても、以下の手順で確認できます。
-
「OAuthクライアントID」から確認した「クライアントID」の名前を選択
-
画面右側に「クライアントID」と「クライアントシークレット」が表示されるのでこれらをメモ
クライアント実装
前述の事前準備が終わっていれば、あとはクライアント側(実際にOpenIDを利用してサービス提供したい側)の実装になります。
Google自体は専用のライブラリを出していてそちら を利用することが推奨されていますが…今回は処理フローをおさえたいのであえて独自でやってみます。
概要
シーケンス図を使っておおまかな流れを確認しておきます。
よく見かける処理の概略図(最初に掲載したのも含めて)だけだとわかりにくい部分があるので、きちんとリクエストレスポンスの組み合わせで記述しきってみました。
詳細
クライアントが提供する「ログイン画面」を表示します。
この「ログイン画面」にはいわゆる「Googleでログイン」的なボタンが含まれています。
以下はイメージ図。
「Googleでログイン」ボタンを押下するとGoogleのログイン画面に遷移します。
このときリクエストするAPIおよびパラーメーター(「Googleでログイン」ボタンを押下した時に遷移する先)は以下の通りです。
HTTPリクエスト
GET https://accounts.google.com/o/oauth2/v2/auth |
パラメーター |
必須 |
説明 |
client_id |
必須 |
事前準備の「認証情報設定」で取得した「クライアントID」を指定。 |
response_type |
必須 |
OpenID Connect で指定されているレスポンスタイプを指定。今回のフローは code 。
選択できる値は以下。
code
token
id_token
id_token token
code id_token
code token
code id_token token
none
|
scope |
必須 |
事前準備の「OAuth同意画面」で指定した「Google API の スコープ」を指定。
複数指定する場合は半角スペースでつないで指定。
OpenID Connect の場合、 openid を必ず含める。
今回は openid , email , profile 。 |
redirect_uri |
必須 |
事前準備の「認証情報設定」で指定した「承認済みのリダイレクトURI」のうちのいずれかを指定。指定するURIは完全一致なので要注意。 |
nonce |
必須 |
ランダムな値。
リプレイ攻撃対策。 |
state |
推奨 |
ランダムな値。
CSRF対策。 |
access_type |
|
以下のいずれかを選択。
|
prompt |
|
以下から選択。複数選択する場合は空白でつなぐ。
|
サンプル
https://accounts.google.com/o/oauth2/v2/auth?
client_id=348089316378.apps.googleusercontent.com&
response_type=code&
scope=openid%20email%20profile&
redirect_uri=http://localhost:8080/&
nonce=eW4VCp2R&
state=Xw4Vf6Yh
参考
ログイン画面が表示されたらエンドユーザーはID、パスワード、2段階認証を入力します。
エンドユーザーが「情報開示の許可」を行うと「認証情報設定」の「承認済みのリダイレクトURI」で指定したURI(クライアント)にリダイレクトします。
このリダイレクト後、クライアントが受け取るリクエストは以下のようなものになります。
今回はリダイレクト先が http://localhost:8080
なのでリクエスト先も localhost です。
おさえておきたいのは「 code
パラーメーターに認可コードが入っている」という点です。
HTTPリクエスト
GET http://localhost:8080/ |
パラメーター |
必須 |
説明 |
code |
|
認可コード。
トークンエンドポイントに対するリクエストで利用する。
response_type に code が含まれていれば取得できる。 |
サンプル
http://localhost:8080/?
state=Xw4Vf6Yh&
code=4%2FvAHuTzYJdTF0USpYZnE0QrrrQcu9uNehLga_WjUF02GDm3vE326PJpAokqLB4WCCvPUzfjry0qPmAUaNK3bCoyw&
scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&
authuser=0&
session_state=bf96cbcd87c4297bbd8d1db4c5f7294521b04868..8cca&
prompt=consent
「認可コード」を受け取ることができれば「トークンエンドポイント」に対してリクエストを投げることで「アクセストークン」および「IDトークン」を取得できます。
このときのリクエストおよびレスポンスは以下のようなものになります。
HTTPリクエスト
POST https://oauth2.googleapis.com/token |
パラメーター |
必須 |
説明 |
code |
必須 |
認可コード。
認可リクエストのレスポンス(リダイレクト)に含まれる認可コード。 |
client_id |
必須 |
クライアントID。
事前準備の認証情報設定で取得したクライアントID。 |
client_secret |
必須 |
クライアントパスワード。
事前準備の認証情報設定で取得したクライアントシークレット。 |
redirect_uri |
必須 |
リダイレクトURI。
事前準備の認証情報設定で設定した承認済みのリダイレクトURI。 |
grant_type |
|
authorization_code 固定。 |
サンプル
POST https://oauth2.googleapis.com/token HTTP/1.1
User-Agent: Fiddler
Content-Type: application/x-www-form-urlencoded
Host: oauth2.googleapis.com
Content-Length: 292
code=4/vAHuTzYJdTF0USpYZnE0QrrrQcu9uNehLga_WjUF02GDm3vE326PJpAokqLB4WCCvPUzfjry0qPmAUaNK3bCoyw&
client_id=348089316378.apps.googleusercontent.com&
client_secret=************&
redirect_uri=http%3A%2F%2Flocalhost%3A8080%2F&
grant_type=authorization_code
レスポンス
パラメーター |
説明 |
access_token |
アクセストークン |
expires_in |
アクセストークンの有効期間(秒) |
id_token |
IDトークン |
token_type |
返却したトークンの種類。Bearer 固定。 |
refresh_token |
リフレッシュトークン。
access_type: offline のときに返却される。 |
サンプル
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Vary: X-Origin
Vary: Referer
Date: Sun, 05 Jan 2020 02:44:28 GMT
Server: scaffolding on HTTPServer2
Cache-Control: private
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
Accept-Ranges: none
Vary: Origin,Accept-Encoding
Content-Length: 1563
{
"access_token": "ya29.Il-4B_UUTvXwxGRLfV3W_4Uw-wJB3SjBGkuEj1foJFKSNE0B9wsTFZCNv7w6Q8uLYgxKEGECEeeWF0RiZMsbxZxijj21uGEcB4gEjys8J2ewydqtLCdgSMKbeGujDqHL8w",
"expires_in": 3600,
"scope": "https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjdoemN2eHFuZ25idTMzZ3B4MzlzYWFxMmF1cnEyZnY4bXg5cTM1bTciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIzNDgwODkzMTYzNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIzNDgwODkzMTYzNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiI5MzY3Njc2OTgzODIzNTgzMjM0NjkiLCJlbWFpbCI6InRzdXlvc2hpLnRhbmFrYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IjJqb1ZFOHpDM3BCSnlDaDNvSzlhRkEiLCJub25jZSI6ImVXNFZDcDJSIiwibmFtZSI6IueUsOS4reWJmyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vLU1RWmtDcjZrQVVrL0FBQUFBQUFBQUFJL0FBQUFBQUFBQUFBL1BrVDhCM2puYWdBWE5yWlE5cWFXR2RVZE1FbmVMOVB3NXgvczk2LWMvcGhvdG8uanBnIiwiZ2l2ZW5fbmFtZSI6IuWJmyIsImZhbWlseV9uYW1lIjoi55Sw5LitIiwibG9jYWxlIjoiamEiLCJpYXQiOjE1NzgxOTIyNjgsImV4cCI6MTU3ODE5NTg2OH0.VRxXxVi9Vw05zURdrR9RoCO3bCpPxUDteoVy8SaFhGU0Y0TjMH6eG0Jfqrz4Epdm82-22_xiIGnA7GEMdnxnbPiuZAfpYWTdmC_LmgY8Ww5GfFH6YkLNL0pmZGuyXtQ_Fzn5T9hUVhQTELw-6IuiTNyfCEaLbqFy7lqmlaox5xDVYSyZ42dUL3Z5sY5DEfSi_o52SSBOkmLtcHMb74rtzxhkn36WEYqcnGy9R3q1S5H8hoQw4jsd7qV79RwUtiCRFoMRyt_yu4YiJYs02ANTE0-Q9zcY7LsIovM0QL_SLIs28nHbfFR-aeK1qV9bpcHM7nVlMQv8fD1wK_66TavSOQ"
}
id_token
(IDトークン) はJWT形式のデータなので、Base64URLデコードを行うと中身を取り出すことができます。
署名は展開できないので、それ以外のヘッダーとペイロードについて展開したものを以下に載せます。
"id_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjdoemN2eHFuZ25idTMzZ3B4MzlzYWFxMmF1cnEyZnY4bXg5cTM1bTciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIzNDgwODkzMTYzNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIzNDgwODkzMTYzNzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiI5MzY3Njc2OTgzODIzNTgzMjM0NjkiLCJlbWFpbCI6InRzdXlvc2hpLnRhbmFrYUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IjJqb1ZFOHpDM3BCSnlDaDNvSzlhRkEiLCJub25jZSI6ImVXNFZDcDJSIiwibmFtZSI6IueUsOS4reWJmyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vLU1RWmtDcjZrQVVrL0FBQUFBQUFBQUFJL0FBQUFBQUFBQUFBL1BrVDhCM2puYWdBWE5yWlE5cWFXR2RVZE1FbmVMOVB3NXgvczk2LWMvcGhvdG8uanBnIiwiZ2l2ZW5fbmFtZSI6IuWJmyIsImZhbWlseV9uYW1lIjoi55Sw5LitIiwibG9jYWxlIjoiamEiLCJpYXQiOjE1NzgxOTIyNjgsImV4cCI6MTU3ODE5NTg2OH0.VRxXxVi9Vw05zURdrR9RoCO3bCpPxUDteoVy8SaFhGU0Y0TjMH6eG0Jfqrz4Epdm82-22_xiIGnA7GEMdnxnbPiuZAfpYWTdmC_LmgY8Ww5GfFH6YkLNL0pmZGuyXtQ_Fzn5T9hUVhQTELw-6IuiTNyfCEaLbqFy7lqmlaox5xDVYSyZ42dUL3Z5sY5DEfSi_o52SSBOkmLtcHMb74rtzxhkn36WEYqcnGy9R3q1S5H8hoQw4jsd7qV79RwUtiCRFoMRyt_yu4YiJYs02ANTE0-Q9zcY7LsIovM0QL_SLIs28nHbfFR-aeK1qV9bpcHM7nVlMQv8fD1wK_66TavSOQ"
↓ Base64URLデコード
// ヘッダー
{
"alg": "HS256",
"kid": "7hzcvxqngnbu33gpx39saaq2aurq2fv8mx9q35m7",
"typ": "JWT"
}
// ペイロード
{
"iss": "https://accounts.google.com",
"azp": "348089316378.apps.googleusercontent.com",
"aud": "348089316378.apps.googleusercontent.com",
"sub": "936767698382358323469",
"email": "tsuyoshi.tanaka@gmail.com",
"email_verified": true,
"at_hash": "2joVE8zC3pBJyCh3oK9aFA",
"nonce": "eW4VCp2R",
"name": "田中剛",
"picture": "https://lh3.googleusercontent.com/-MQZkCr6kAUk/AAAAAAAAAAI/AAAAAAAAAAA/PkT8B3jnagAXNrZQ9qaWGdUdMEneL9Pw5x/s96-c/photo.jpg",
"given_name": "剛",
"family_name": "田中",
"locale": "ja",
"iat": 1578192268,
"exp": 1578195868
}
参考
追加の個人情報が欲しい場合は「個人情報エンドポイント」に対して「アクセストークン」を付けてリクエストします。
HTTPリクエスト
GET https://www.googleapis.com/userinfo/v2/me |
ヘッダー |
説明 |
Authorization |
Bearerスキーマにトークンリクエストで取得したアクセストークンを指定。 |
サンプル
GET https://www.googleapis.com/userinfo/v2/me HTTP/1.1
Host: www.googleapis.com
Content-length: 0
Authorization: Bearer ya29.Il-4B_UUTvXwxGRLfV3W_4Uw-wJB3SjBGkuEj1foJFKSNE0B9wsTFZCNv7w6Q8uLYgxKEGECEeeWF0RiZMsbxZxijj21uGEcB4gEjys8J2ewydqtLCdgSMKbeGujDqHL8w
レスポンス
パラメーター |
説明 |
name |
エンドユーザーの名前。 |
given_name |
エンドユーザーの名(下の名前)。 |
family_name |
エンドユーザーの姓(上の名前)。 |
nickname |
エンドユーザーのニックネーム。 |
profile |
エンドユーザーのプロフィールページURL。 |
picture |
エンドユーザーのプロフィール画像URL。 |
email |
エンドユーザーのメールアドレス。 |
gender |
エンドユーザーの姓別。 |
birthdate |
エンドユーザーの誕生日。YYYY-MM-DD フォーマット。 |
locale |
エンドユーザーのロケール。 |
address |
エンドユーザーの住所。 |
updated_at |
エンドユーザーの情報が更新された日付(エポック秒)。 |
サンプル
HTTP/1.1 200 OK
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Date: Sun, 05 Jan 2020 02:49:41 GMT
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json; charset=UTF-8
Vary: X-Origin
Vary: Referer
Server: ESF
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
Accept-Ranges: none
Vary: Origin,Accept-Encoding
Content-Length: 322
{
"id": "936767698382358323469",
"email": "tsuyoshi.tanaka@gmail.com",
"verified_email": true,
"name": "田中剛",
"given_name": "剛",
"family_name": "田中",
"picture": "https://lh3.googleusercontent.com/-MQZkCr6kAUk/AAAAAAAAAAI/AAAAAAAAAAA/PkT8B3jnagAXNrZQ9qaWGdUdMEneL9Pw5x/s96-c/photo.jpg",
"locale": "ja"
}
参考
今回は Google の OpenID Connect を例に 「OpenID Connect の具体的な実装方法」 についてまとめました。
参考になったでしょうか?
本記事がお役に立っていると嬉しいです!!
参考記事