Slide 1

Slide 1 text

Python Social Authで学ぶ、
 OAuth2.0認可コードフローにおける
 異常系への対処
 @yktakaha4 / Yuuki Takahashi
 2022/10/15
 PyCon JP 2022


Slide 2

Slide 2 text

自己紹介
 2


Slide 3

Slide 3 text

- 自分が実務で困ったり混乱した部分について、今後同じ問題に悩む人が減るとよい と思って明文化したものです
 - 認証 / 認可の専門家でなく、Webアプリケーション開発者としての個人的な経験に基づきます 
 
 
 
 
 
 - 精度向上のため、フィードバックお待ちしています🙏
 - わかりづらい部分があった 
 - 変なこと / 間違ったことを言っていた 
 - こういうことも知りたかった...などなど 
 本セッションの目的
 OAuthにおける異常系処理について一緒に考えましょう
 3


Slide 4

Slide 4 text

Python Social Authを使ったソーシャルログイン機能の実装 
 4


Slide 5

Slide 5 text

Python Social Authを使ったソーシャルログイン機能の実装 
 そもそもソーシャルログインってなんだっけ?
 5


Slide 6

Slide 6 text

Python Social Authを使ったソーシャルログイン機能の実装 
 そもそもソーシャルログインってなんだっけ?
 6


Slide 7

Slide 7 text

Python Social Authを使ったソーシャルログイン機能の実装 
 そもそもソーシャルログインってなんだっけ?
 7


Slide 8

Slide 8 text

Python Social Authを使ったソーシャルログイン機能の実装 
 そもそもソーシャルログインってなんだっけ?
 8
 連携SNSでアカウント作成していればすぐログインできる 氏名やメールアドレスなどの情報が自動入力される場合も

Slide 9

Slide 9 text

Python Social Authを使ったソーシャルログイン機能の実装 
 9
 😍
 最高かよ


Slide 10

Slide 10 text

Python Social Authを使ったソーシャルログイン機能の実装 
 10
 🙄
 でも作るの難しいんでしょう?


Slide 11

Slide 11 text

Python Social Authを使ったソーシャルログイン機能の実装 
 - https://github.com/python-social-auth/social-core
 - ソーシャルログインに関する共通的な機能やインターフェースを提供 
 - IdP(認証プロバイダ)毎の固有実装 
 - GitHub / Google / Twitter / etc... 
 - 処理フロー中に生じるエラーを抽象化してくれる 
 - あとで説明します
 
 - https://github.com/python-social-auth/social-app-django
 - social-coreをDjangoに組み込むための各種機能を提供 
 - Model / View / Middleware / etc... 
 Python Social Authを試してみる
 11


Slide 12

Slide 12 text

Python Social Authを使ったソーシャルログイン機能の実装 
 - https://github.com/yktakaha4/learn-oauth-errors-with-python-social-auth
 サンプルアプリケーションをつくってみる
 12


Slide 13

Slide 13 text

- GitHubの設定画面からOAuthアプリケーションとして登録
 Python Social Authを使ったソーシャルログイン機能の実装 
 サンプルアプリケーションの実装
 13
 Secret

Slide 14

Slide 14 text

- いくつかの定数と、
 
 
 
 
 
 - URL設定をしたら…
 
 Python Social Authを使ったソーシャルログイン機能の実装 
 サンプルアプリケーションの実装
 14
 AUTHENTICATION_BACKENDS = ( # GitHub認証をバックエンドとして追加 "social_core.backends.github.GithubOAuth2" , "django.contrib.auth.backends.ModelBackend" , ) # GitHubから発行されたクライアント IDとシークレットを設定 SOCIAL_AUTH_GITHUB_KEY = environ.get( "SOCIAL_AUTH_GITHUB_KEY" ) SOCIAL_AUTH_GITHUB_SECRET = environ.get( "SOCIAL_AUTH_GITHUB_SECRET" ) urlpatterns = [ path("social/", include("social_django.urls" , namespace="social")), ] Secret

Slide 15

Slide 15 text

Python Social Authを使ったソーシャルログイン機能の実装 
 サンプルアプリケーションの実装
 15


Slide 16

Slide 16 text

Python Social Authを使ったソーシャルログイン機能の実装 
 サンプルアプリケーションの実装
 16


Slide 17

Slide 17 text

Python Social Authを使ったソーシャルログイン機能の実装 
 サンプルアプリケーションの実装
 17


Slide 18

Slide 18 text

Python Social Authを使ったソーシャルログイン機能の実装 
 18
 🤗
 なんか知らんが動いた〜ハッピ〜


Slide 19

Slide 19 text

Python Social Authを使ったソーシャルログイン機能の実装 
 19
 🧽
 SEVERAL DAYS LATER...


Slide 20

Slide 20 text

Python Social Authを使ったソーシャルログイン機能の実装 
 20
 エラー監視サービスに謎の通知が


Slide 21

Slide 21 text

Python Social Authを使ったソーシャルログイン機能の実装 
 21
 ⚰


Slide 22

Slide 22 text

Python Social Authを使ったソーシャルログイン機能の実装 
 - SocialAuthBaseExceptionを継承した例外がたくさん用意されている
 - AuthException / AuthFailed / AuthCanceled / AuthUnknownError / AuthTokenError / AuthMissingParameter / AuthAlreadyAssociated / WrongBackend / NotAllowedToDisconnect / AuthStateMissing / AuthStateForbidden / AuthTokenRevoked / AuthUnreachableProvider… 
 - ????????? 
 
 - Middlewareで上記例外を捕捉すればよいらしい
 - SocialAuthExceptionMiddleware をそのまま追加 or 継承して追加 
 エラーハンドリングを追加
 22
 class CustomizedSocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware): def get_message(self, request, exception): if isinstance(exception, AuthException): # TODO: 本当はここで適切なエラーメッセージを表示したい pass return f"ログインに失敗しました(例外 : {exception.__class__. __name__})"

Slide 23

Slide 23 text

Python Social Authを使ったソーシャルログイン機能の実装 
 エラーハンドリングを追加
 23
 このエラーがなんだったのか、 解決のためにユーザーに何をしてもらうべきか はわからず…

Slide 24

Slide 24 text

Python Social Authを使ったソーシャルログイン機能の実装 
 24
 😂
 なんもわからんけど直った〜よかった〜


Slide 25

Slide 25 text

(とならないために、)OAuthのおさらい
 25


Slide 26

Slide 26 text

OAuthのおさらい
 - ユーザーが所有している権限をサードパーティーのアプリケーションへ
 認可(Authorization)する手段を標準化したプロトコル
 - 現在の最新はOAuth2.0(RFC6749) だが、OAuth2.1もドラフト策定中 
 
 - 認可≠認証(Authentication)
 - OAuthを用いたソーシャルログイン機能とは 
 - プロフィール情報へのアクセス権を認可できる人を本人とみなしてログインを許可する 
 機能と言えそう
 
 - User Social Authを利用する≠OAuthを利用する
 - IdP(Identity Provider)が提供する機構にあわせたバックエンド実装が選択される 
 - GitHubはOAuth2.0 / TwitchはOIDC / Twitterはつい最近までOAuth1.0の独自拡張 
 OAuthについて
 26


Slide 27

Slide 27 text

OAuthのおさらい
 - 今回は認可コード付与(Authorization Code Grant)フローを例にします
 - WebアプリケーションでOAuthを利用する場合のスタンダード 
 - 実際の挙動はIdP毎に独自拡張がされている場合も少なくない 
 
 - が、発表時間の都合上、大幅にカットしてお伝えします😢
 - エラーの原因となりやすい構造 にフォーカスします
 
 - 正常系の動作が気になる方は…
 - 資料の末尾に簡単な補足をつけています 
 - Auth屋さんが執筆されている書籍がわかりやすいのでオススメ 
 - https://www.amazon.co.jp/dp/B07XT8H2YG 
 OAuthについて
 27


Slide 28

Slide 28 text

28
 OAuthのおさらい
 ユーザーの Webブラウザ バックエンド サーバー 認可 エンドポイント プロフィール取得 エンドポイント トークン エンドポイント 1.
 2-1.
 処理シーケンス
 Code State Code State Token Code Token State Token State State State State Code Session →
 State Session ←
 2-2.
 3-1.
 3-2.
 Secret Secret 💃
 遷移がめっちゃ複雑なので 
 OAuth Danceと呼ばれます 
 
 今回はポイントを絞って 
 説明します


Slide 29

Slide 29 text

29
 OAuthのおさらい
 ポイントその1
 ユーザーの Webブラウザ バックエンド サーバー 認可 エンドポイント プロフィール取得 エンドポイント トークン エンドポイント Secret 󰔡
 登場人物が多く、
 数秒の処理の間で
 インターネット間の通信が 
 多く発生します


Slide 30

Slide 30 text

302 Moved Temporarily
 Location: https://github.com/login/oauth/authorize?state=sss…
 302 Moved Temporarily
 Location: http://localhost:8000/social/complete/github ?code=ttt…
 30
 OAuthのおさらい
 ポイントその2
 🔄
 HTTPリダイレクトと
 クエリ文字列を駆使して、
 認可に必要な情報を 
 やりとりします
 State Code State

Slide 31

Slide 31 text

31
 OAuthのおさらい
 ポイントその3
 🍪
 セキュリティ上の理由から、 
 セッション≒HTTP Cookie
 に依存する実装がほとんどです 
 ユーザーの Webブラウザ バックエンド サーバー 2-1.
 Code State State State Session →
 State 3-2.
 Secret Session ←


Slide 32

Slide 32 text

OAuthのおさらい
 32
 🤖
 わからなくても安心してください
 退屈なことは Python Social Auth がやってくれます
 正 常 系


Slide 33

Slide 33 text

OAuthのおさらい
 33
 🧠
 人間はもっと刺激的なことについて
 考えていきましょう…
 異 常 系


Slide 34

Slide 34 text

現実世界において生じる異常系処理
 34


Slide 35

Slide 35 text

- 認可処理について言及している?例外クラス 
 - AuthFailed … 何らかの理由で認証に失敗した
 - AuthCanceled … ユーザーによって認証がキャンセルされた 
 - AuthUnknownError … 不明なエラーによって認証プロセスが停止した 
 - AuthTokenError … 不正なアクセストークンによって操作がおこなわれた 
 - AuthMissingParameter… 必要なパラメータが不足していた
 - AuthStateMissing … 認可サーバーのレスポンスに state パラメーターがない 
 - AuthStateForbidden … 返却された state パラメーターが送信されたものと異なる 
 
 - 認可成功後の処理について言及している?例外クラス 
 - AuthAlreadyAssociated … アカウントが既に別のユーザーに紐付けられている 
 - NotAllowedToDisconnect … アカウント切断時、ユーザーが他の認証 / 認可SNSを持っていない 
 - AuthTokenRevoked … ユーザーがIdP側で access_token を取り消している 
 
 - その他の例外クラス
 - WrongBackend … URLに指定されたIdPのエンドポイントが無効 
 - AuthUnreachableProvider … サーバーがIdPのエンドポイントと通信できなかった 
 - SocialAuthBaseException … 上記例外の基底クラス
 現実世界において生じる異常系処理 
 どんなエラーが起きるか in Python Social Auth
 35
 引用: https://python-social-auth.readthedocs.io/en/latest/exceptions.html (2022年10月11日閲覧 作成者翻訳) 
 🤔


Slide 36

Slide 36 text

- 認可処理について言及している?例外クラス 
 - AuthFailed … 何らかの理由で認証に失敗した
 - AuthCanceled … ユーザーによって認証がキャンセルされた 
 - AuthUnknownError … 不明なエラーによって認証プロセスが停止した 
 - AuthTokenError … 不正なアクセストークンによって操作がおこなわれた 
 - AuthMissingParameter… 必要なパラメータが不足していた
 - AuthStateMissing … 認可サーバーのレスポンスに state パラメーターがない 
 - AuthStateForbidden … 返却された state パラメーターが送信されたものと異なる 
 
 - 認可成功後の処理について言及している?例外クラス 
 - AuthAlreadyAssociated … アカウントが既に別のユーザーに紐付けられている 
 - NotAllowedToDisconnect … アカウント切断時、ユーザーが他の認証 / 認可SNSを持っていない 
 - AuthTokenRevoked … ユーザーがIdP側で access_token を取り消している 
 
 - その他の例外クラス
 - WrongBackend … URLに指定されたIdPのエンドポイントが無効 
 - AuthUnreachableProvider … サーバーがIdPのエンドポイントと通信できなかった 
 - SocialAuthBaseException … 上記例外の基底クラス
 現実世界において生じる異常系処理 
 どんなエラーが起きるか in Python Social Auth
 36
 引用: https://python-social-auth.readthedocs.io/en/latest/exceptions.html (2022年10月11日閲覧 作成者翻訳) 
 どうしてこの分類なのかよくわからない システムエラー?ユーザー入力の問題? 🤔


Slide 37

Slide 37 text

現実世界において生じる異常系処理 
 - RFC6749「The OAuth 2.0 Authorization Framework」において、
 エラーについて説明しているのは主に以下
 - 4. Obtaining Authorization 〜 4.1. Authorization Code Grant 〜 4.1.2. Authorization Response 
 - 要約:認可エンドポイントへの要求失敗時 のエラーレスポンスの 仕様を定めるよ
 
 - 5. Issuing an Access Token 〜 5.2. Error Response 
 - 要約:トークンエンドポイントへの要求失敗時 のエラーレスポンスの 仕様を定めるよ
 
 - 7. Accessing Protected Resource 〜 7.2. Error Response 
 - 要約:保護対象リソースへの要求失敗時 も何かしらのエラーを返却すべきだけど、 
 この仕様の範疇外だよ
 
 - 8. Extensibility 〜 8.5. Defining Additional Error Codes 
 - 要約:他にもエラーコードが必要な場合は 追加で定義してもいいよ 
 - 結局は各IdPが固有に実装したもの を適切に処理する必要がある 
 RFCに立ち返ってみると
 37


Slide 38

Slide 38 text

38
 現実世界において生じる異常系処理 
 ユーザーの Webブラウザ バックエンド サーバー 認可 エンドポイント プロフィール取得 エンドポイント トークン エンドポイント 1.
 2-1.
 どこでエラーが起きるか
 Code State Code State Token Code Token State Token State State State State Code Session →
 State Session ←
 2-2.
 3-1.
 3-2.
 Secret Secret A. 認可エンドポイントで 
 画面表示時にエラー発生、 
 バックエンドサーバーに 
 HTTP302でリダイレクト 
 
 B. 認可エンドポイントで 
 認可操作実行時にエラー発生、 
 バックエンドサーバーに 
 HTTP302でリダイレクト 
 
 C. トークンエンドポイントで 
 トークン交換時にエラー発生、 
 バックエンドサーバーに 
 HTTP400でレスポンス返却 
 
 D. (リソース取得でエラー発生) 
 A. B. C. D.

Slide 39

Slide 39 text

現実世界において生じる異常系処理 
 - 認可エンドポイントで発生するエラー(A. B.) 
 - invalid_request … リクエストが不正な形式である
 - access_denied … リクエストが拒否された
 - invalid_scope … 要求されたスコープが適切でない
 - unauthorized_client … 付与タイプが許可されていない
 - unsupported_response_type … 付与タイプに対応していない
 - server_error … 予期せぬエラー
 - temporarily_unavailable … 過負荷等でリクエストを処理不能
 
 - トークンエンドポイントで発生するエラー(C.) 
 - invalid_request … リクエストが不正な形式である
 - invalid_grant … 不正な認可コード等で認可できない 
 - invalid_client … クライアントが認証できない
 - invalid_scope … 要求されたスコープが適切でない
 - unauthorized_client … 認可フローが許可されていない
 - unsupported_grant_type … 付与タイプに対応していない
 
 - リソース取得時に発生するエラー(D.) 
 - 仕様上は未定義のため、今回は割愛
 どんなエラーが起きるか in RFC
 39
 HTTP/1.1 302 Found
 Location: http://localhost:8000/cb?error=access_denied&state=xyz
 HTTP/1.1 400 Bad Request
 Content-Type: application/json;charset=UTF-8
 
 {
 "error":"invalid_request"
 }
 引用: https://openid-foundation-japan.github.io/rfc6749.ja.html (2022年10月11日閲覧) 


Slide 40

Slide 40 text

現実世界において生じる異常系処理 
 - 認可エンドポイントで発生するエラー(A. B.) 
 - invalid_request … リクエストが不正な形式である
 - access_denied … リクエストが拒否された
 - invalid_scope … 要求されたスコープが適切でない
 - unauthorized_client … 付与タイプが許可されていない
 - unsupported_response_type … 付与タイプに対応していない
 - server_error … 予期せぬエラー
 - temporarily_unavailable … 過負荷等でリクエストを処理不能
 
 - トークンエンドポイントで発生するエラー(C.) 
 - invalid_request … リクエストが不正な形式である
 - invalid_grant … 不正な認可コード等で認可できない 
 - invalid_client … クライアントが認証できない
 - invalid_scope … 要求されたスコープが適切でない
 - unauthorized_client … 認可フローが許可されていない
 - unsupported_grant_type … 付与タイプに対応していない
 
 - リソース取得時に発生するエラー(D.) 
 - 仕様上は未定義のため、今回は割愛
 どんなエラーが起きるか in RFC
 40
 HTTP/1.1 302 Found
 Location: http://localhost:8000/cb?error=access_denied&state=xyz
 HTTP/1.1 400 Bad Request
 Content-Type: application/json;charset=UTF-8
 
 {
 "error":"invalid_request"
 }
 引用: https://openid-foundation-japan.github.io/rfc6749.ja.html (2022年10月11日閲覧) 
 ユーザー操作と紐づくものもある RFC側で網羅的に定義することを目指してい ない(カスタムエラーも可のため) 基本、リダイレクト時にクエリ文字列 が欠けるとエラーになる

Slide 41

Slide 41 text

- 認可処理について言及している?例外 
 - AuthFailed … 何らかの理由で認証に失敗した
 - AuthCanceled … ユーザーによって認証がキャンセルされた 
 - AuthUnknownError … 不明なエラーによって認証プロセスが停止した 
 - AuthTokenError … 不正なアクセストークンによって操作がおこなわれた 
 - AuthMissingParameter… 必要なパラメータが不足していた
 - AuthStateMissing … 認可サーバーのレスポンスに state パラメーターがない 
 - AuthStateForbidden … 返却された state パラメーターが送信されたものと異なる 
 
 - 認可成功後の処理について言及している?例外 
 - AuthAlreadyAssociated … アカウントが既に別のユーザーに紐付けられている 
 - NotAllowedToDisconnect … アカウント切断時、ユーザーが他の認証 / 認可SNSを持っていない 
 - AuthTokenRevoked … ユーザーがIdP側で access_token を取り消している 
 
 - その他の例外
 - WrongBackend … URLに指定されたIdPのエンドポイントが無効 
 - AuthUnreachableProvider … サーバーがIdPのエンドポイントと通信できなかった 
 - SocialAuthBaseException … 上記例外の基底クラス
 現実世界において生じる異常系処理 
 改めて、どんなエラーが起きるか in Python Social Auth
 41
 引用: https://python-social-auth.readthedocs.io/en/latest/exceptions.html (2022年10月11日閲覧 作成者翻訳) 
 RFCには存在しない観点だったことがわかる RFCと対応していそうなものも多い

Slide 42

Slide 42 text

現実世界において生じる異常系処理 
 コードも見てみよう
 42
 class BaseOAuth2(OAuthAuth): """Base class for OAuth2 providers. OAuth2 details at: https://datatracker.ietf.org/doc/html/rfc6749 """ # 中略 def process_error(self, data): if data.get('error'): if 'denied' in data['error'] or 'cancelled' in data['error']: raise AuthCanceled(self, data.get('error_description' , '')) raise AuthFailed(self, data.get('error_description' ) or data['error']) elif 'denied' in data: raise AuthCanceled(self, data['denied']) class GithubOAuth2(BaseOAuth2): """Github OAuth authentication backend""" エラー判定処理 RFCで規定されるエラーをざっくり例外に束ねる https://github.com/python-social-auth/social-core/blob/master/social_core/backends/oauth.py 
 https://github.com/python-social-auth/social-core/blob/master/social_core/backends/github.py 
 GitHub用の認証バックエンド 共通クラスを継承して動作を共有している

Slide 43

Slide 43 text

現実世界において生じる異常系処理 
 - リダイレクトの過程でクエリ文字列が変わるとエラー
 - ユーザーがメーラー / WebView / Curl等の特殊な環境からアクセスしてきたら? 
 - ユーザーが手動でURLを書き換えたら? 
 
 
 - 認可処理の途中でセッション状態が変わるとエラー
 - 認可URLを別のブラウザにコピペされたら? 
 - 複数のタブで並行に認可操作をおこなわれたら? 
 
 - IdPのエンドポイントが落ちてたらエラー
 - Connection / Read Timeoutなんかもありえる 
 - バックエンド側の実装によっては、 
 urllib.error.HTTPError が投げられることも… 
 ポイントを踏まえたエラーが起きやすい箇所
 43


Slide 44

Slide 44 text

現実世界において生じる異常系処理 
 44
 💡
 勘所がつかめてきたところで、
 解決へ向けて考えていきましょう


Slide 45

Slide 45 text

現実世界において生じる異常系処理 
 - 4種類に分けて整理してみます
 分類を試みる
 45
 エラーの種類 例外の一例 解消方法 監視レベル a. ユーザの操作に 起因する認可エラー AuthCanceled AuthTokenRevoked ??? ??? b. ユーザーの環境に 起因する認可エラー AuthMissingParameter AuthStateForbidden ??? ??? c. IdPに起因する 認可エラー AuthUnreachableProvider HTTPError ??? ??? d. その他のエラー AuthAlreadyAssociated WrongBackend ??? ???

Slide 46

Slide 46 text

現実世界において生じる異常系処理 
 - 認可フロー中でユーザーが明示的におこなった操作により発生したエラー
 - 認可画面で拒否をおこなった 
 - 一度認可したが後日取り消しをおこなったあと、再度認可フローに入った 
 
 - 解決するには、もう一度最初から認可フローをおこなってもらう
 - もしもユーザーが誤って認可拒否をおこなっていたのであれば再操作で解決 
 - ユーザーが意識的に拒否をおこなっていたのであれば、そもそもエラーではない 
 
 - エラー監視は不要 or 発生件数のトレンドがわかれば十分
 - ユーザー操作に起因するため、0件になることはない 
 - 監視ツール(例: Sentry)に通知した上でIgnore or エラー通知しない 
 - OSS側のバグが不安なら発生数を追えるようにしてもよいかも 
 a. ユーザの操作に起因する認可エラー
 46


Slide 47

Slide 47 text

現実世界において生じる異常系処理 
 a. に対するユーザーの体験イメージ
 47
 ℹ 認証がキャンセルされました。 ログインを希望する場合は、再度操作 をおこなってください。 何も表示しなくても 十分かも?

Slide 48

Slide 48 text

現実世界において生じる異常系処理 
 - ユーザーの環境(Webブラウザ)に起因して発生したエラー
 - フロー中に何かしらの事情でクエリパラメータ / Cookie(≒セッション)が失われた 
 
 - 解決するには、もう一度最初から認可フローをおこなってもらう(再)
 - プログラム側のバグではないため再操作してもらう他ない 
 - 正常な認可フローを阻害しうる要因について設定を確認してもらうお願いする 
 - シークレットブラウザやCookieの設定 
 - 一連の操作を同一のWebブラウザで操作しているかどうか 
 
 - エラー監視は発生件数のトレンドがわかれば十分
 - a. よりは重要度は高いが、ブラウザに起因する以上0件にはなりえない 
 - 一時的 or あるタイミング以降大量発生している…といったことには気づけた方がよい 
 b. ユーザーの環境に起因する認可エラー
 48


Slide 49

Slide 49 text

現実世界において生じる異常系処理 
 b. に対するユーザーの体験イメージ
 49
 🚫認証に失敗しました。 以下を確認し、再度操作をおこなってく ださい。 ・Cookieが有効されていること ・対応ブラウザでサイトを閲覧しているこ と

Slide 50

Slide 50 text

現実世界において生じる異常系処理 
 - IdP(認可サーバー)の状態に起因して発生したエラー
 - 一時的に高負荷になっている 
 
 - 解決するには、もう一度最初から認可フローをおこなってもらう(再々)
 - 自分たちでコントロールできる範囲でないためもう一度操作してもらうほかない 
 - 一方で、ユーザー体験の毀損度合いは高い ので相応なメッセージを出したほうがよさそう 
 - 外部サービスのエラーであること 
 - 解消まで待ってもらうしかないこと 
 - 他の認証方法でログイン可能ならそれを試してもらうこと 
 
 - エラー監視は急激な発生を検知できたほうがよい
 - 時間が長引くようなら障害報を出したほうがいいケースも 
 - HTTPリダイレクト先でエラーになる可能性も高いので検知方法は要検討 
 c. IdPに起因する認可エラー
 50


Slide 51

Slide 51 text

現実世界において生じる異常系処理 
 c. に対するユーザーの体験イメージ
 51
 🚫連携サービスの一時的な不具合により 認証ができない状況になっています。 しばらく待ってから再度操作するか、他の ログイン方法をお試しください。 こういう体験になる可能性も高いので、 ログインページにFAQへの導線があってもいいかも

Slide 52

Slide 52 text

- a.~c.以外で発生していて、OAuthに関連すると思われるエラー
 - エラーは多岐に渡るため、網羅的に考えるのがベター 
 
 - 解決するには、プログラムの修正が必要な可能性が高い
 - 例えばAuthAlreadyAssociated であれば、アプリケーションの要件にあわせて仕様検討が必要 
 - ひとつのアカウントに複数SNS認証を実施できる場合は要注意かも 
 - WrongBackend であれば、OAuthのアプリケーション設定を壊してしまった可能性がある 
 
 - エラー監視は注視する必要あり
 - よくわからないものを d. のエラーとしてまず捉え、a.~c.に分類できるか考える 
 - 分類できそうならエラーメッセージを返却するようにして監視レベルを下げる 
 - 分類できなさそうならバグとして起票 & 対処 
 現実世界において生じる異常系処理 
 d. その他のエラー
 52


Slide 53

Slide 53 text

Python Social Authを使ったソーシャルログイン機能の実装 
 d. に対するユーザーの体験イメージ
 53
 🚫認証に失敗しました。 恐れ入りますが、しばらくしてから再度操 作をおこなってください。 とりあえずリトライを促すメッセージは 出しておき、エラー発生したら検知 & エンジニアで調査する

Slide 54

Slide 54 text

まとめ
 54


Slide 55

Slide 55 text

まとめ
 ソーシャルログインはUXを高める素晴らしい機能です😍
 55


Slide 56

Slide 56 text

56
 まとめ
 ユーザーの Webブラウザ バックエンド サーバー 認可 エンドポイント プロフィール取得 エンドポイント トークン エンドポイント 1.
 2-1.
 OAuthはとても複雑💃
 Code State Code State Token Code Token State Token State State State State Code Session →
 State Session ←
 2-2.
 3-1.
 3-2.
 Secret Secret 🤖
  正常系はライブラリに任せて、 
 🧠
 異常系に注力しましょう 


Slide 57

Slide 57 text

まとめ
 - まず d. を疑い、a. ~ c.に分類する or 不具合解消を目指しましょう
 異常系処理の乗りこなし方🐎
 57
 エラーの種類 一例 解消方法 監視レベル a. ユーザの操作に 起因する認可エラー ・ユーザが認可を拒否した ・認可後に許可を取り消した 再操作 してもらう 不要or発生数 b. ユーザーの環境に 起因する認可エラー ・クエリパラメータが消えた ・セッションが消えた 再操作 してもらう 発生数 c. IdPに起因する 認可エラー ・認可サーバーが落ちてる 再操作 してもらう 急激な増加 発生数 d. その他のエラー ・まだよくわからないエラー エラーにより 異なる 要対応

Slide 58

Slide 58 text

参考文献
 58


Slide 59

Slide 59 text

- Justin Richer, Antonio Sanso.『OAuth徹底入門 セキュアな認可システムを適用するための原則と実践』. 翔泳社, 2019, p464
 
 - Auth屋.『雰囲気で使わずきちんと理解する!整理してOAuth2.0を使うためのチュートリアルガイド』. インプレスR&D, 2019, p94
 
 - Peter Yaworski.『リアルワールドバグハンティング』. オーム社, 2020, p281
 
 - 芝田 将.『実践Django Pythonによる本格Webアプリケーション開発』. 翔泳社, 2021, p312
 59
 参考文献


Slide 60

Slide 60 text

- D. Hardt, Ed.「The OAuth 2.0 Authorization Framework」. https://www.ietf.org/rfc/rfc6749.html, (2022年10月11日閲覧)
 
 - OpenID Foundation Japan. 「The OAuth 2.0 Authorization Framework」. https://openid-foundation-japan.github.io/rfc6749.ja.html, (2022年10月11日閲覧)
 
 - Aaron Paracki.「OAuth Community Site」. https://oauth.net/, (2022年10月11日閲覧).
 
 - Python Social Auth.「Welcome to Python Social Auth’s documentation!」.
 https://python-social-auth.readthedocs.io/en/latest/, (2022年10月11日閲覧).
 
 - GitHub Docs.「Building OAuth Apps」.
 https://docs.github.com/en/developers/apps/building-oauth-apps, (2022年10月11日閲覧).
 60
 参考文献


Slide 61

Slide 61 text

補足資料
 61


Slide 62

Slide 62 text

OAuth認可コード付与フローの概要 
 - リソース所有者(Resource Owner)
 - 私たちが構築したWebアプリケーションの利用者 
 - プロフィールを所有しており、IdPに情報の登録をおこなっている 
 
 - クライアント(Client)
 - 私たちが構築したWebアプリケーションのバックエンド サーバー
 - 利用者のプロフィール情報を取得したいと思っている 
 
 - 認可サーバー(Authorization Server)
 - IdPが構築したOAuthのエンドポイント 
 - 認可エンドポイントとトークンエンドポイント を持つ
 
 - 保護対象リソース(Protected Resource)
 - IdPが構築したプロフィール取得のエンドポイント 
 - 認可されたクライアントに対して情報を提供する 
 認可コード付与フローにおける登場人物
 62


Slide 63

Slide 63 text

63
 OAuth認可コード付与フローの概要 
 ● URLへのアクセスに対してログインページを 返却
 1. ログインページ表示
 ユーザーの Webブラウザ バックエンド サーバー 1.
 GET http://localhost:8000/accounts/ 
 200 OK


Slide 64

Slide 64 text

64
 OAuth認可コード付与フローの概要 
 ● ログインボタンの押下に対してSNS認可に必 要な情報をクエリ文字列に含むURLを 
 構築(state, scope, client_id, etc...) 
 
 ● GitHubの認可画面にリダイレクト 
 2.SNS認可画面へ遷移
 ユーザーの Webブラウザ バックエンド サーバー 認可 エンドポイント 2-1.
 State State State Session →
 GET http://localhost:8000/social/login/github 
 302 Moved Temporarily 
 Location: https://github.com/login/oauth/authorize?state=sss… 
 GET https://github.com/login/oauth/authorize?state=sss… 
 State 200 OK
 2-2.
 👶
 Secret

Slide 65

Slide 65 text

65
 OAuth認可コード付与フローの概要 
 ユーザーの Webブラウザ 認可 エンドポイント ● ユーザーが許可ボタンを押すと 
 認可エンドポイントより 認可コード(code)が発行さ れる
 ○ 1回限りアクセストークンに交換できる値 
 
 ● 認可コードをクエリ文字列に含めた上で、 
 元いたWebサイトにリダイレクト 
 3.プロフィール情報取得
 3-1.
 3-2.
 State Code State Code POST https://github.com/login/oauth/authorize 
 302 Moved Temporarily 
 Location: http://localhost:8000/social/complete/github?code=ttt… 
 👶
 (次ページへ)


Slide 66

Slide 66 text

66
 OAuth認可コード付与フローの概要 
 ユーザーの Webブラウザ バックエンド サーバー プロフィール取得 エンドポイント トークン エンドポイント ● 認可コード&Stateがバックエンド サーバーに渡される 
 
 ● トークンエンドポイントを使い 
 認可トークンをアクセストークン (access_token)に交換
 
 ● トークンを付与してプロフィール取 得エンドポイントへ
 
 ● プロフィール情報を元にアカウン ト作成やログインを実施 
 3-2.
 State Code State Session ←
 Token Token GET http://localhost:8000/social/complete/github?code=ttt&state=sss… 
 POST https://github.com/login/oauth/access_token 
 Code 200 OK
 200 OK
 200 OK
 👶
 Secret Secret Token GET https://api.github.com/user 
 3.プロフィール情報取得


Slide 67

Slide 67 text

補足資料
 Middlewareの実装イメージ
 67
 class CustomizedSocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware): def get_message (self, request, exception): if isinstance (exception , AuthCanceled): # a. ユーザの操作に起因する認可エラー return "ℹ認証がキャンセルされました。ログインを希望する場合は、再度操作をおこなってください。 " elif isinstance (exception , AuthMissingParameter): # b. ユーザーの環境に起因する認可エラー # エラー発生を監視システムに通知する(その上で無視する) self.capture_exception(exception) return "🚫認証に失敗しました。以下を確認し、再度操作をおこなってください。〜 " elif isinstance (exception , HTTPError): # c. IdP に起因する認可エラー # エラー発生を監視システムに通知する(短時間で一定量発生しない限り無視する) self.capture_exception(exception) return ( "🚫連携サービスの一時的な不具合により認証ができない状況になっています。〜 " ) else: # d. その他のエラー # エラー発生を監視システムに通知し、 a. ~ c. のエラーに振り分けてよいか検討する self.capture_exception(exception) return "🚫認証に失敗しました。恐れ入りますが、しばらくしてから再度操作をおこなってください。 " def capture_exception (self, exception): # Sentry などのエラー監視システムにエラー情報を送信 pass (こぼれ話)デフォルトだと AuthExceptionを継承しない例外は Middlewareにやってこないので、 process_exception を override して 例外発生したrequest.pathや exceptionの内容をみてハンドリング をおこなう必要があるので注意