社内向け勉強会で発表した内容です。 30分でと書いてありますが、実際には50分かかりました。 また時間の関係で結構省いたりしている箇所があります。
2020/07/19追記 ご指摘をいただいた箇所を多々修正いたしました。 特にOIDCとSPAの章が初版とは大幅に変更されていますのでご注意ください。
Twitter: @DddEndow
30分でOpenID Connect完全に理解したと言えるようになる勉強会
View Slide
OAuthとOAuth認証とOpenID Connect» OAuthは権限移譲のプロトコル» 「OAuth認証」はプロフィールAPIによる認証» OpenID ConnectはIDトークンによる認証2
いきなり言われてもわからん…認証・認可 権限委譲IDトークン3
OAuthとOpenID Connectの関係OAuthOpenIDConnect拡張仕様基礎となるOAuthを理解していないとOpenID Connectを理解するのは難しい4
お品書き» そもそもOAuthってなに?» OAuth認証ってなにが問題なの?» OpenID ConnectはOAuthとなにが違うの?» ネイティブアプリやSPAではどう使うべき?5
今日しない話» OAuthの歴史» OAuth2.0の詳細» IDトークンの具体的な検証方法6
※注意点わかりやすさを優先しているため、一部説明や前提を省略しています。あらかじめご了承ください。7
0.認証・認可8
そもそも認証・認可って?認可(AuthZ)● リソースアクセス権限をサードパーティのアプリに移譲すること● 誰かに許可を与えること認証(AuthN)● 通信の相手が誰であるかを確認すること● 個人の身元の確認9
1.OAuth10
OAuthとは?» 権限委譲のプロトコル» 認可のプロトコル11
OAuthのメリット安全性サードパーティアプリにはリソースオーナーのID・パスワードを教える必要がない権限委譲ユーザのリソースへのアクセス権限をサードパーティのアプリに移譲することができる12
OAuthのメリット安全性サードパーティアプリにはリソースオーナーのID・パスワードを教える必要がない権限委譲ユーザのリソースへのアクセス権限をサードパーティのアプリに移譲することができる正直なに言ってるかわからん…13
OAuthの流れを追ってみる14
リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)OAuthの登場人物15
①アプリ上で「Google Photoから画像をダウンロードする」をクリック①リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)16
①②②Google OAuthに対してGoogle Photoのアクセス権を要求リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)17
①②③③「Google Photoへのアクセス権をクライアントに委譲すること」についてユーザーの意思を確認リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)18
①②③④④権限委譲に同意するリソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)19
①②③④⑤⑤権限が委譲された証としてアクセストークンを発行して、クライアントに渡すリソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)20
①②③④⑤⑥⑥クライアントはアクセストークンをもってGooglePhotoのAPIにアクセスし、ユーザーの画像を要求リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)21
①②③④⑤⑥⑦⑦Google Photoはアクセストークンの有効性と紐づく権限を確認問題がなければユーザーの画像をクライアントに渡すリソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)22
①②③④⑤⑥⑦リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)こんな大変なことをやるメリットってなに?23
①②③④⑤⑥⑦リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)Point 1ユーザーのID・パスワードがクライアントに伝わっていない→ID・パスワード入力に起因する脆弱性を防げる24
①②③④⑤⑥⑦リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)Point 2ユーザーが許可してはじめて権限が委譲される● 勝手にユーザー情報へのアクセスが許可されることはない● 必要最小限の権限のみ委譲25
①②③④⑤⑥⑦リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)Point 3アクセストークンで管理している● 誰のリソースのどのような権限に紐づいているのか● クライアントのリソースへのアクセス許可26
①②③④⑤⑥⑦リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)リソースサーバー(Google Photo)Point 3アクセストークンで管理している● 誰のリソースのどのような権限に紐づいているのか● クライアントのリソースへのアクセス許可OAuthはアクセストークンを発行するためのルール27
クライアント(自分の作ったアプリ)リソースサーバー(Google Photo)OAuthのまとめが への限定的なアクセスをアクセストークンの発行方法(あるユーザーへの必要最小限の範囲の)可能にするための28
2.OAuth認証29
認証のためのプロトコルという勘違いOAuth» 権限委譲のためのプロトコル» アプリのユーザー認証の話は出てこないリソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)①権限の委譲依頼 ②許可の確認③委譲許可④アクセストークン発行30
リソースオーナー(ユーザー)③委譲許可認証のためのプロトコルという勘違いOAuth» 権限委譲のためのプロトコル» アプリのユーザー認証の話は出てこないクライアント(自分の作ったアプリ)認可サーバー(Google OAuth)①権限の委譲依頼 ②許可の確認④アクセストークン発行じゃあなんでOAuth認証なんて言葉があるの?31
プロフィールAPI(リソースサーバー)OAuth + プロフィールAPIだいたいこいつのせい32
プロフィールAPI(リソースサーバー)OAuth + プロフィールAPIプロフィールAPI» プロフィール情報を提供するAPIクライアント(自分の作ったアプリ)①アクセストークン②プロフィール情報を提供33
☆☆アクセストークンに紐づくプロフィール情報を要求→返されたプロフィール情報のデータで認証リソースオーナー(ユーザー)クライアント(自分の作ったアプリ)認可サーバー(Google OAuth)プロフィールAPI(リソースサーバー)OAuth認証の仕組み34
OAuth認証の脆弱性35
Step 1悪意あるアプリが他人のアクセストークンを取得→見た目上は普通に動作Aさん(ユーザー)悪意のあるアプリ認可サーバー(Google OAuth)OAuth認証の脆弱性リソースサーバー(Google Photo)普通に動作Aさんのアクセストークン36
Step 2盗んだアクセストークンに差し替え→別のユーザーとしてログインできてしまう!悪意のあるユーザー クライアント認可サーバー(Google OAuth)プロフィールAPI(リソースサーバー)OAuth認証の脆弱性盗んだAさんのアクセストークンに差し替えAさんのプロフィール情報37
Step 2盗んだアクセストークンに差し替え→別のユーザーとしてログインできてしまう!悪意のあるユーザー クライアント認可サーバー(Google OAuth)プロフィールAPI(リソースサーバー)OAuth認証の脆弱性盗んだAさんのアクセストークンに差し替えAさんのプロフィール情報Aさんとしてログイン38
補足:OAuth認証39FacebookのOAuth認証» アクセストークン検証用エンドポイント⋄ /debug_token⋄ 検証用データを取得» アクセストークンを検証⋄ すり替えられていないことを担保
プロフィールAPIOAuth認証のまとめから取得したプロフィールの情報を使用して認証&ログインする方法悪意あるアプリによってアクセストークンを盗まれた場合、別のユーザーになりすましができる脆弱性がある40
3.OpenID Connect41
OpenID Connectとは?OpenID Connect(OIDC)» クライアントが認証できるプロトコル» リソースアクセス(認可)も可能» OAuthの拡張仕様OpenID Connect (OIDC)= OAuth + IDトークン + UserInfoエンドポイント42
OpenID Connectとは?OpenID Connect(OIDC)» クライアントが認証できるプロトコル» リソースアクセス(認可)も可能» OAuthの拡張仕様OpenID Connect (OIDC)= OAuth + IDトークン + UserInfoエンドポイントPoint! 43
OpenID Connectとは?三種類のフロー» 認可フロー⋄ 安全にclient secretを保存できるクライアント用(サーバー)» インプリシットフロー⋄ 安全にclient secretを保存できないクライアント用(アプリ)» ハイブリットフロー⋄ 安全にアクセストークンや IDトークンを保存できないクライアント用(SPA)44
OpenID Connectとは?三種類のフロー» 認可フロー⋄ 安全にclient secretを保存できるクライアント用(サーバー)» インプリシットフロー⋄ 安全にclient secretを保存できないクライアント用(アプリ)» ハイブリットフロー⋄ 安全にアクセストークンや IDトークンを保存できないクライアント用(SPA)あとで説明45
OpenID Connectとは?OAuthのグラントとOIDCのフロー46OAuth OIDC認可コードグラントインプリシットグラントリソースオーナパスワードクレデンシャルグラントクライアント クレデンシャルグラントハイブリッドグラント認可コードフローインプリシットフロー--ハイブリッドフロー
OpenID Connectの流れを追ってみる47
エンドユーザー リライング・パーティIDプロバイダUserInfoエンドOIDCの登場人物48
エンドユーザー リライング・パーティIDプロバイダUserInfoエンドOIDCの登場人物なんか見たことあるような…?49
OAuth:リソースオーナーOIDC:エンドユーザーOAuth:クライアントOIDC:リライング・パーティOAuth:認可サーバーOIDC:IDプロバイダOAuth:リソースサーバーOIDC:UserInfoエンドOIDCの登場人物Pointロールの呼び方が違うだけで、役割はOAuthと同じ!50
①②③④①〜④OAuthと全く同じエンドユーザー リライング・パーティIDプロバイダUserInfoエンド51
①②③④⑤⑤アクセストークンと一緒にIDトークンを渡すエンドユーザー リライング・パーティIDプロバイダUserInfoエンドアクセストークンIDトークン52
①②③④⑤⑥IDトークンを検証し、ユーザーを認証するエンドユーザー リライング・パーティIDプロバイダUserInfoエンドIDトークンを検証⑥53
①②③④⑤⑥IDトークンを検証し、ユーザーを認証する→ログイン完了!エンドユーザー リライング・パーティIDプロバイダUserInfoエンドIDトークンを検証⑥54
①②③④⑤⑦⑧⑦〜⑧(必要があれば)アクセストークンを使用してプロフィール情報を取得例:名前、アイコン画像、その他個人情報エンドユーザー リライング・パーティIDプロバイダUserInfoエンド⑥アクセストークン55
IDトークンがなぜ必要なのか?56
IDトークンとは?IDトークン» リライング・パーティがエンドユーザを認証するために使用» 署名付きのJSON Web Token(JWT)» ユーザーIDや検証に必要な情報、署名などが含まれる57
IDトークンとは?IDトークンの中身» 誰が、いつ、どの範囲で、と言った(IdPから見た)一連の認証イベントしての情報が含まれている⋄ ユーザーIDや検証に必要な情報、署名⋄ 発行日時、有効期限⋄ 許可された権限58適切に検証することで意図するやりとりが行われたことを確認できる
IDトークンとは?IDトークンの中身» 誰が、いつ、どの範囲で、と言った(IdPから見た)一連の認証イベントしての情報が含まれている⋄ ユーザーIDや検証に必要な情報、署名⋄ 発行日時、有効期限⋄ 許可された権限59適切に検証することで意図するやりとりが行われたことを確認できるPoint!
不正ログインを防ぐ仕組み60
注意!IDトークン自体の具体的な検証方法については触れません61
nonceパラメータ» リプレイアタックを防ぐためのパラメータ» IDトークンに含まれる» 毎回異なるランダムな文字列を使用する62
①②③④⑤⑦⑧エンドユーザー リライング・パーティIDプロバイダUserInfoエンド⑥OpenID Connect63
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ UserInfo①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証リダイレクト②認証リクエスト64
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト65
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト66なんか違くない?
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエストわかりやすくするために簡略化してました67なんか違くない?
nonceの使い方68
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト①セッションに紐付けてnonce=xyzを発行nonce=xyz69
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{②②②リダイレクトで認証リクエストnonce=xyz70
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{②②②nonceを認証リクエストに含めるnonce=xyznonce=xyz71
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{③〜④③〜④● ID・パスワードの入力● ユーザ情報提供についての同意確認nonce=xyznonce=xyz72
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{☆☆☆認証レスポンス(認可コード)を返す→認可コードはワンタイム(一度使用したら無効化)nonce=xyz73
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{☆☆= nonce=xyzIDプロバイダ上では認可コードとnonce=xyzは紐付けられているnonce=xyz74
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑤⑤● 認可コードを使用してトークンをリクエスト● アクセストークンやIDトークンを取得nonce=xyz75
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑤= nonce=xyznonce=xyzが設定されたIDトークンを返すnonce=xyznonce=xyz76
OpenID Connectのシーケンス図ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤エンドユーザーリライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑥⑥nonceの値が一致するか検証nonce=xyznonce=xyz77
OIDCで入れ替えを受けた場合78
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{☆☆nonce=xyz認可コードを入れ替える場合悪意あるユーザー認可コードを入れ替えちゃえ!79
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{☆☆認可コードを入れ替える場合悪意あるユーザー認可コードを入れ替えちゃえ!nonce=abcnonce=xyz別のリライング・パーティで発行した認可コード→nonceが異なる80
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑤= nonce=abc認可コードに紐づくnonceが含まれるIDトークンを返すnonce=abcnonce=xyz悪意あるユーザー認可コードを入れ替える場合81
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑥セッションに紐づくnonceとIDトークンのnonceが異なる!nonce=abcnonce=xyz悪意あるユーザー認可コードを入れ替える場合82
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑥セッションに紐づくnonceとIDトークンのnonceが異なる!nonce=abcnonce=xyz悪意あるユーザーログイン失敗!認可コードを入れ替える場合83
①②③④リライング・パーティIDプロバイダ⑥補足:リプレイアタック対策84⑤エンドユーザー一度使用した認可コードは無効化される(ワンタイム)
①②③④リライング・パーティIDプロバイダ⑥補足:リプレイアタック対策85⑤エンドユーザー通信が傍受され、使用済みの認可コードが盗まれた場合悪意あるユーザー
①②③④リライング・パーティIDプロバイダ⑥補足:リプレイアタック対策86⑤使用済みの認可コードの場合既に無効化されているのでトークンは発行されない悪意あるユーザー
“アンチパターン”nonceを固定値にした場合87
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{☆☆認可コードを入れ替える場合悪意あるユーザー認可コードを入れ替えちゃえ!nonce=xyznonce=xyz異なるリライング・パーティでもnonceの値が固定88
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑥セッションに紐づくnonceとIDトークンのnonceが一致!nonce=xyznonce=xyz悪意あるユーザー認可コードを入れ替える場合89
ユーザ リライング・パーティ IDプロバイダ①ログイン③〜④認証認可コードリダイレクトトークンリクエスト⑤アクセストークン ・IDトークン⑥IDトークン検証①②③④⑤リライング・パーティIDプロバイダ⑥リダイレクト②認証リクエスト{⑥セッションに紐づくnonceとIDトークンのnonceが一致!nonce=xyznonce=xyz悪意あるユーザー認可コードを入れ替える場合不正ログイン成功!ぐへへ90
OpenID Connectまとめ» クライアントが認証できるプロトコル» IDトークンを使用して安全に認証する» Nonceはランダムな文字列にし、必ず検証する» 認可コードをワンタイムにすることでリプレイアタックを防ぐ91
4.OIDCとネイティブアプリ92
ネイティブアプリでのOIDC» client secretを秘匿に保てない⋄ 認可コードフローだとトークン取得時に漏洩する⋄ 自由に認可コードやトークンが発行可能に!» 長期間有効なAPIアクセスを必要とするリライング・パーティ IDプロバイダ認証リクエスト(client_id, client_secret)認証レスポンス(認可コード)93
インプリシットフローClientSecretを安全に保存できないため、それを使用せず認可コードフローを簡略化したフロー» クライアントの認証がIDプロバイダで行われない⋄ client secretを使用せずにトークンの発行が可能» リフレッシュトークンの発行が禁止⋄ 再取得時にはインプリシットフローを再度行う必要がある94
インプリシットフローのシーケンス図ユーザ リライング・パーティ IDプロバイダ UserInfoログイン認証アクセストークン・IDトークンリダイレクトIDトークン検証リダイレクト認証リクエスト95
シーケンス図の比較ユーザ リライング・パーティ IDプロバイダログイン認証認可コードリダイレクトトークンリクエストアクセストークン ・IDトークンIDトークン検証リダイレクト認証リクエストユーザ リライング・パーティ IDプロバイダログイン認証アクセストークン・IDトークンリダイレクトIDトークン検証リダイレクト認証リクエストインプリシットフロー 認可コードフロー96
シーケンス図の比較ユーザ リライング・パーティ IDプロバイダログイン認証認可コードリダイレクトトークンリクエストアクセストークン ・IDトークンIDトークン検証リダイレクト認証リクエストユーザ リライング・パーティ IDプロバイダログイン認証アクセストークン・IDトークンリダイレクトIDトークン検証リダイレクト認証リクエストインプリシットフロー 認可コードフローかなりシンプルに!直接受け取るのが特徴97
インプリシットフローの問題点リソースアクセスと併用する場合» IdPによってはアクセストークンの有効期限が切れるたびに再認証が必要⋄ 数分〜数時間に一度ログイン画面が表示されてしまう→どうしてもユーザー体験が下がってしまう98
インプリシットフローの問題点リソースアクセスと併用する場合» IdPによってはアクセストークンの有効期限が切れるたびに再認証が必要⋄ 数分〜数時間に一度ログイン画面が表示されてしまう→どうしてもユーザー体験が下がってしまう99安全にリフレッシュトークンが発行できればなー・・・
PKCE(ピクシー)» OAuthの拡張仕様» パブリッククライアントで認可コードフローを安全に行う仕組み⋄ ClientSecretを安全に保存できないことへの対策⋄ 認可コードの横取り攻撃を防ぐ⋄ 認可コードフローなので Refresh Tokenが取得可能安全にリフレッシュトークンが取得できる!100
パブリックとコンフィデンシャル» パブリッククライアント⋄ クライアントの認証情報( ClientID, ClientSecret)をセキュアに保存できないクライアント⋄ ネイティブアプリやブラウザベースのアプリ» コンフィデンシャルクライアント⋄ クライアントの認証情報をセキュアに保存できるクライアント⋄ サーバーなど101
認可コード横取り攻撃» 以下のような場合、開発者が入れ替わりを防ぐのは困難⋄ 悪意のあるアプリが同時にインストールされている⋄ 悪意あるアプリで同じカスタムスキームが設定されている⋄ 悪意あるアプリがクライアント IDを知っている102
認可コード横取り攻撃» 以下のような場合、開発者が入れ替わりを防ぐのは困難⋄ 悪意のあるアプリが同時にインストールされている⋄ 悪意あるアプリで同じカスタムスキームが設定されている⋄ 悪意あるアプリがクライアント IDを知っているPKCEでの対策入れ替わりが発生した場合、アクセストークンを発行させない103
PKCE(ピクシー)での対策以下の三つの値を使用して対策» code_verifier⋄ ランダムな文字列» code_challenge⋄ code_verifierにcode_challenge_methodを適応して算出した値» code_challenge_method⋄ plainまたはS256104
認可コードフロー+PKCEのシーケンス図ユーザ ネイティブアプリログイン認証カスタムスキームによるアプリ起動(認可コード)IDトークン検証認可リクエストcode_challengecode_challenge_methodブラウザcode_challengecode_challenge_methodを保存認可レスポンス(認可コード)アクセストークン ・IDトークンcode_verifier検証認可リクエストcode_challengecode_challenge_methodIDプロバイダ(認可サーバー)トークンリクエスト認可コード、code_verifier105
認可コードフロー+PKCEのシーケンス図ユーザ ネイティブアプリ IDプロバイダ(認可サーバー)ログイン認証カスタムスキームによるアプリ起動(認可コード)IDトークン検証認可リクエストcode_challengecode_challenge_methodブラウザcode_challengecode_challenge_methodを保存認可レスポンス(認可コード)アクセストークン ・IDトークンcode_verifier検証認可リクエストcode_challengecode_challenge_methodトークンリクエスト認可コード、code_verifierStep 1code_challengeとcode_challenge_methodをIDプロバイダに保存106
認可コードフロー+PKCEのシーケンス図ユーザ ネイティブアプリ IDプロバイダ(認可サーバー)ログイン認証カスタムスキームによるアプリ起動(認可コード)IDトークン検証認可リクエストcode_challengecode_challenge_methodブラウザcode_challengecode_challenge_methodを保存認可レスポンス(認可コード)トークンリクエスト認可コード、code_verifierアクセストークン ・IDトークンcode_verifier検証認可リクエストcode_challengecode_challenge_methodStep 2code_verifierをトークンリクエストに含める107
認可コードフロー+PKCEのシーケンス図ユーザ ネイティブアプリ IDプロバイダ(認可サーバー)ログイン認証カスタムスキームによるアプリ起動(認可コード)IDトークン検証認可リクエストcode_challengecode_challenge_methodブラウザcode_challengecode_challenge_methodを保存認可レスポンス(認可コード)トークンリクエスト認可コード、code_verifierアクセストークン ・IDトークンcode_verifier検証認可リクエストcode_challengecode_challenge_methodStep 3事前に保存したcode_challengeとcode_challenge_methodでcode_verifierを検証108
認可コード横取り攻撃を受けた場合109
認可コード横取り攻撃を受けた場合ユーザ ネイティブアプリログイン認証カスタムスキームによるアプリ起動(認可コード)IDトークン検証認可リクエストcode_challengecode_challenge_methodブラウザcode_challengecode_challenge_methodを保存認可レスポンス(認可コード)アクセストークン ・IDトークンcode_verifier検証認可リクエストcode_challengecode_challenge_methodIDプロバイダ(認可サーバー)トークンリクエスト認可コード、code_verifier悪意あるアプリ横取りしたぜ!110
認可コード横取り攻撃を受けた場合ユーザ ネイティブアプリログイン認証カスタムスキームによるアプリ起動(認可コード)IDトークン検証認可リクエストcode_challengecode_challenge_methodブラウザcode_challengecode_challenge_methodを保存認可レスポンス(認可コード)アクセストークン ・IDトークンcode_verifier検証認可リクエストcode_challengecode_challenge_methodIDプロバイダ(認可サーバー)トークンリクエスト認可コード、code_verifier悪意あるアプリcode_verifierがわからない…ぐぬぬ検証失敗!トークンレスポンスは無し111
OIDCとネイティブアプリのまとめ» client secretを保存するのは厳禁» 認証だけでいいならインプリシットフロー(非推奨)» UXを考えるなら認証コードフロー+PKCE(推奨)112TipsOAuth2.1ではインプリシットフローはなくなるので注意
5.OIDCとSPA113
SPAでのOIDCSPA(ブラウザ)では安全にアクセストークンやIDトークンが保存できない» Web Storage(localStorageやsessionStorage)に保存⋄ 自分のアプリでXSS対策をしても他のJSが悪さをすると台無し» iFrameでIdPとのセッションをメモリ上に格納⋄ サードパーティCookieを保持するのはNGな流れになってきた→今のところベストプラクティスはない114
SPAでのOIDC二つの対策» 認可コードフロー+独自のセッション» RefreshTokenをローテーションする(Auth0など)115
認可コードフロー+独自セッション116
認可コードフロー+独自セッション» OIDCをログイン管理に使用しない⋄ 認証のみに使用する⋄ 独自のセッション(cookieなど)でログイン管理を行うフロントネイティブアプリIDプロバイダバックエンド認可コード トークンリクエストアクセストークンIDトークン独自セッション117
ユーザリライング・パーティフロント IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード )リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証独自セッションnonceやcode_challengeを生成118
ユーザリライング・パーティフロント IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード )リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証独自セッションPoint 1フロントとバックエンドであらかじめセッションを構築→バックエンドでnonceやPKCEに必要な値を生成し、セッションに紐づけるnonceやcode_challengeを生成119
ユーザリライング・パーティフロント IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード )リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証独自セッションPoint 2認可コードをバックエンドにリクエストするnonceやcode_challengeを生成120
ユーザリライング・パーティフロント IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード )リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証独自セッションPoint 3バックエンドでトークンを取得nonceやcode_challengeを生成121
ユーザリライング・パーティフロント IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード )リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証独自セッションPoint 4セッションに紐づくnonceやcode_verifierで検証する必要であればこのタイミングでUserInfoからプロフィール情報を取得のするnonceやcode_challengeを生成122
ユーザリライング・パーティフロント IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード )リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証独自セッションPoint 5以後全てセッションで管理するアクセストークンやIDトークンはフロントに一切渡らないnonceやcode_challengeを生成123
認可コードフロー+独自セッションまとめ» フロントにトークン類を保持しないのでセキュア⋄ セッションにユーザー IDなどの識別子を含めないように注意⋄ CSRF対策も忘れずに» SPAとネイティブアプリを同時に使用する場合はバックエンドの実装を分ける必要がある» 実装が割と手間124
RefreshTokenのローテーション125
RefreshTokenをローテーションする» RefreshTokenを使用して新しいトークンを発行する時⋄ →古いRefreshTokenは無効化&新しいRefreshTokenを発行⋄ OAuthの仕様だと自動では無効化されない» 古いRefreshTokenでアクセスされた場合、全てのRefreshTokenを無効化» IdP側に仕組みが必要(Auth0などがサポート)126
クライアント(SPA)認可サーバー(Google OAuth)RefreshTokenのローテーションリフレッシュトークン新しいアクセストークン新しいIDトークン新しいリフレッシュトークン通常のトークンリフレッシュ127
クライアント(SPA)認可サーバー(Google OAuth)RefreshTokenのローテーションリフレッシュトークン新しいアクセストークン新しいIDトークン新しいリフレッシュトークン通常のトークンリフレッシュ悪意あるユーザー使用済みのリフレッシュトークン横取りしたぜ!128
クライアント(SPA)認可サーバー(Google OAuth)RefreshTokenのローテーションリフレッシュトークン新しいアクセストークン新しいIDトークン新しいリフレッシュトークン通常のトークンリフレッシュ悪意あるユーザー無効化されてないからアクセスし放題!2回目以降は新しいリフレッシュトークンを使用する129
クライアント(SPA)認可サーバー(Google OAuth)RefreshTokenのローテーションリフレッシュトークン新しいアクセストークン新しいIDトークン新しいリフレッシュトークンAuth0のトークンリフレッシュ 古いリフレッシュトークンを無効化130
クライアント(SPA)認可サーバー(Google OAuth)RefreshTokenのローテーションリフレッシュトークン新しいアクセストークン新しいIDトークン新しいリフレッシュトークンAuth0のトークンリフレッシュ悪意あるユーザー使用済みのリフレッシュトークン横取りしたぜ!131
クライアント(SPA)認可サーバー(Google OAuth)RefreshTokenのローテーションリフレッシュトークン新しいアクセストークン新しいIDトークン新しいリフレッシュトークンAuth0のトークンリフレッシュ悪意あるユーザー無効化されてるからトークンを更新できない…既に無効化されてるリフレッシュトークンが使用された!132
クライアント(SPA)認可サーバー(Google OAuth)RefreshTokenのローテーションリフレッシュトークン新しいアクセストークン新しいIDトークン新しいリフレッシュトークンAuth0のトークンリフレッシュ悪意あるユーザー無効化されてるからトークンを更新できない…既に無効化されてるリフレッシュトークンが使用された!トークンが漏洩している可能性あり!未使用のリフレッシュトークンも無効化!133
RefreshTokenのローテーションまとめ» リフレッシュトークンを盗難されても被害を最小限にとどめる» IdP側での実装が必要» トークンを盗まれるの自体は防げない⋄ トークンの有効期限を短くすることで対策134
OIDCとSPAのまとめ» SPAでトークンを安全に保管する方法は今のところ存在しない» OIDCを認証として使用し、独自のセッションでログイン管理を行う(推奨)» RefreshTokenをローテーションする(Auth0など)135
6.おまけハイブリッドフロー136
ハイブリッドフロー» ハイブリッドフロー⋄ 認可コードフローとインプリシットフローを合わせたもの⋄ 認可レスポンスとトークンレスポンスの両方があるのが特徴フロントネイティブアプリIDプロバイダバックエンド認可コード トークンリクエストアクセストークンIDトークン認証完了137認証リクエスト認可コード・アクセストークン
ユーザリライング・パーティネイティブアプリ IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード ・アクセストークン)リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証認証完了138
ユーザリライング・パーティネイティブアプリ IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード ・アクセストークン)リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証認証完了139Point 1認可コードとアクセストークンを受け取る。アクセストークンはネイティブアプリ側に保存
ユーザリライング・パーティネイティブアプリ IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード ・アクセストークン)リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証認証完了140Point 2認可コードをバックエンドにリクエストする
ユーザリライング・パーティネイティブアプリ IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード ・アクセストークン)リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証認証完了141Point 3バックエンドでセキュアにトークンを取得可能
ユーザリライング・パーティネイティブアプリ IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード ・アクセストークン)リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証認証完了142Point 4IDトークンを検証する
ユーザリライング・パーティネイティブアプリ IDプロバイダ UserInfoリライング・パーティバックエンドログイン認証認証レスポンス(認可コード ・アクセストークン)リダイレクト認可コードトークンリクエストアクセストークン ・IDトークンIDトークン検証認証完了143Point 5アクセストークンを保存しているため、ネイティブ側からもリソースにアクセスが可能となる
ハイブリッドフローのまとめ» 認可コードフローとインプリシットフローを合わせたもの⋄ 認可レスポンスとトークンレスポンスの両方があるのが特徴» バックエンド側でセキュアに認証が可能» パブリック(ネイティブアプリ)側からリソースにアクセスすることが可能144
Last.まとめ145
今日の話まとめ» OAuthの概要» OAuth認証はNG» OpenID Connectの検証はしっかりと行う» ネイティブアプリとSPAでのOIDCの注意点146
“IDトークンを検証したからといって安全になるわけではありません。”“OpenID Connectを正しく理解して使用しましょう。”147
Special Thanks!Auth屋● 書籍:雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本● 書籍:OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本ritou● ブログ:r-weblife148
Thank youwatching!遠藤 大輔Twitter: @DddEndow149