Identity Bookmarks 2020

2020年にIdentity系の読んだ記事とかのなかでメモしておきたいもののまとめ。

目次

Digital Identity

AuthN/Z

The Journey to Our New Authentication System

- レガシーなUser Authenticationシステムから新しいシステムのGoIDにどのようにユーザーをマイグレーションしたか
- Gojekが東南アジアでスーパーアプリになると、堅牢なユーザー管理システムが必要となった
    - アカウント作成や認証、支払い延長などのユーザーに関連するシナリオを担うシステム
- Meet GoID
    - GoIDは独立したシステムとして使用されていた
    - サードパーティーを含む全てのサービスが、ユーザーアカウントとIDの管理を行うGoIDを利用している
- Why this and why now?
    - Monolithic Authentication
        - 以前のAuthサービスでは、様々なサービスやプロトコルを介して認証されており、ユーザープロパティの標準化等が困難だった
        - GoIDを使う事で、consumer、driver、merchantなどの全てのユーザーアカウントがGoIDにより管理される
    - Standalone service
    - Decentralised user access management
        - ユーザーデータを処理するエンドポイントにかける認可の管理を単一のサービスで実行可能になった
        - 今まではスコープがサービス毎に管理されていた
    - Integration with 3rd party services
        - サードパーティーにOAuth2.0ベースのAPIを提供
        - “Login with Gojek”
        - note: ”OAuth 2”って書いてたけど、OIDCではなく??
    - Bridging the gap with GoPay!  
        - GoIDを使用すると、サードパーティアプリはGoPayを決済サービスとして統合することもできる
- The Challenges
    - existing user tokens, login flows, registering flows, refresh servicesなどの李ファクタを行った
    - Auth ServiceとGoIDのどちらから発行したトークンも受け入れる必要ががる
    - 段階的なロールアウトのためにFirebase remote configurationを使った
    - ユーザーがログアウトしないで済むように、Auth Serviceで発行したトークンをGoID経由でリフレッシュする際にはQAに時間を割いた
    - 既存のAuth Service以上のセキュリティテスト
    - Auth Serviceの廃止が近づいてきた際には、GoID経由でサインアップするようにリダイレクトをシームレスに行った
- The Big Migration
    - どのような手順でマイグレを行ったのか
    - Addition of new endpoints 
        - Androidでは、ネットワーククライアントはネットワーククライアントを新しいURLで再構成する必要があった
    - Toggle-based redirection
        - フラグを用いて、Auth ServiceからGoIDへのリダイレクトを行う
        - 他のログインAPIやリフレッシュAPIに対しても同様
    - Persisting GoID status & managing existing tokens
        - GoIDでログイン等をしたユーザーのセッションを確率する
        - 次回リダイレクトでGoIDに飛ばされた場合も、ストレージに永続化してセッションを引き継げるようにする
    - Mapping methods for contracts & error codes
    - Maintaining the quality
    - The Final step

OAuth/OIDC

How to use OAuth 2.0 “state” parameter other than CSRF protection

- stateとは
    - [RFC6749](https://tools.ietf.org/html/rfc6749)に定義されているCSRF対策のためのパラメーター   
- クライアント側の実装方法
    - 現在のセッションに紐づくランダム文字列を生成
    - Authorization Requestにstateパラメーターとして含める
    - Authorization Responseに含まれるstateパラメーターを検証する
- PKCE
    - 同じくCSRF対策のための仕組み
    - PKCEを使うと、stateは本来のクライアントのアプリケーション状態に関するデータを転送する目的として使えるかも
- CSRF以外のstateパラメーターの使い方
    - アプリケーションの状態を持たす
        - OAuth完了後のリダイレクト先等
        - クエリパラメーターに乗るので、改竄を防ぐために暗号化と署名が必要
    - セッションに保存されたアプリケーションの状態を確認するための値を持たす
        - stateをAuthorizatin Request時にアプリケーションの状態を元に生成する
        - authorization response時に検証する
        - 一致しない場合、stateもしくはクライアントセッションの値が変更されている可能性がある
  • メモ
    • 記事の主題とはそれるので記述はないが、検証がサーバー側になるのでPKCEの方がIdPとしてはベター(なはず)
    • アプリケーションの状態を持たす使い方は、状態を見て処理したいユースケースは意外とある?
      • 遷移元が複数あって、完了後元のページに戻したい場合とかもredirect_url複数登録しておけば出来るけど、管理めんどいしstateを見て判定できた方が良いのかな?

JWT

Why JWTs Suck as Session Tokens

- JWTの一般的なユースケースは認証
    - セッショントークンやAPIトークンとして使う
- サーバー側でJWTにユーザーIDなどの情報を含めブラウザやAPIに払い出し、セッショントークンとして保持する
- ユーザーが次にサイトに訪れた時は、バックエンドAPIにJWTを渡し、サーバー側で検証を行い自身が払い出したJWTであることを確認する
- メリット
    - 改竄検知等検証が可能
    - ネットワークやデータベースへのリクエストなしにローカルで検証可能
        - note: 
            - JWTにDBの識別子を持たせたりしてる場合は別
            - 大量リクエストを捌くことを考えると、DBリクエストなしにJWT単体で認証が完結するのは強い
    - ステートレス
- Why Do JWTs Suck?
    - 前提
        - ほとんどのサイトがほぼすべての操作でユーザー情報を必要とする
    - Sizeが大きくなってしまう
        - CookieとJWTにユーザーID(abc124)を保存する場合
            - Cookieの場合は6バイト
            - JWTの場合は304バイト
    - JWTを持っていてもどうせDBを引くことになる
        - ほとんどのサイトは頻繁にユーザーデータやユーザーに紐づく様々なDBを操作する
        - JWTを持っているかどうかに関係なく、キャッシュサーバー/データベースと通信する可能性がある
        - JWTのステートレスな利点が活用できない
    - 冗長署名
        - JWTの主なセールスポイントの1つは暗号署名。JWTは暗号で署名されているため、受信側でJWTが有効で信頼できることを確認できる
        - 過去20年間の間のほとんどのWebフレームワークで、セッションCookieを使用すると暗号化署名のメリットも得られる
            - ほとんどのフレームワークはCookieを自動的に暗号化する
        - 認証の場合、JWTは基本的にCookieに保存される
            - CookieとJWTの両方に署名が存在することになる
        - 両方の署名を検証する必要
- What’s a Better Solution?
    - シンプルなウェブサイトを構築している場合はCookieにユーザーIDを直接書き込む
    - スケールさせるためにはmemcachedやredistを利用する
- How Should I Use JWTs?
    - サーバー間またはクライアント間(モバイルアプリやSPAなど)の通信を行う場合のAPIトークンとして使用する
        - ユーザーデータに依存せずに認証データをステートレスな方法で永続化できる
    - ユーザーフェデレーション(シングルサインオンやOpenID Connectなど)
        - サードパーティを介してユーザーのIDを検証する方法が必要

The design of account registration API using JWT

- JWTのレジストレーショントークンでトランザクションを表す
- Backendレジストレーショントークンのペイロードに含まれる登録データを検証する
- バックエンドサーバーはレジストレーショントークンの識別子のみを管理する
    - jti
- ex)
    - ユーザーが利用規約、プライバシーポリシーに同意
        - トークンのagreed_tos、agreed_privacy_policyに各規約のVersionを含める
    - 上記を登録に必要なデータがJWTに全て含まれるまで行う
- 識別子のみが管理されるため、データストアの容量は低く抑えられる
- データストアに一時登録データを保持するAPIと比較して、JWTを使用することで登録フローを変更する際にAPIおよび登録完了処理のロジックを変更するだけで済む

JWT is Awesome: Here's Why

- Pro: JWT is standardized and supported in most languages
    - ほとんどの言語でライブラリが存在する
    - 様々な用途で利用されており、知見も豊富
- Pro: JWT supports user attributes out-of-the box
    - 認証に必要なユーザー属性情報を含めることができる
        - user id
        - dysplay name
        - email address
- Pro: JWT is unicode friendly out-of-the-box
- Pro: JWT doesn’t DDoS authentication servers
    - 認証システムがダウンすると全てがダウンする
    - JWTは非対称キーによって署名され、ローカルで検証できるので、外部サービスを呼び出したり、単一障害点を抱える必要はない
- Pro: Dramatic decrease in latency 
    - ローカルで検証可能なため、APIやデータベースへのリクエストが不要
- Pro: JWT is secure
- Pro: Don’t roll your own
    - 認証に必要な以下のような情報が含まれる、追加も容易
        - a user identifier in the token,
        - a way to retrieve user attributes (another system?)
        - when was the token created (iat) and is it still valid (exp)
        - which settings were used (alg + kid), (really hard to do future upgrade if missing)
        - what created the token and why (issuer + aud) (probably don’t care about that)
- Myth: JWT is decentralized. (It is not unless you make it so)
- Myth: JWT doesn’t support logout or invalidation. (It can with OpenID Connect)
    - JWTの発行者や有効期限の検証はiat、expを用いて行う
        - この場合、任意のトークンを無効化することができない
        - expで定義された期間は有効となってしまう
        - なのでexpは短くすべき
    - 全てのJWTトークンを無効化することは可能
        - 署名に利用する鍵をローテーションする
- Con: JWT token Size
    - 典型的なトークンは、500〜1000バイトほど
    - ペイロードが多いほどサイズも大きくなる
    - JWTトークンは一般的にCookieもしくはAuthorizationヘッダーを用いて、渡される
        - 多くのWebサーバーでリクエストヘッダーのサイズは8kbに制限されている

The Ultimate Guide to handling JWTs on frontend clients (GraphQL)

- JWTはサーバーによって発行されるトークンでユーザー固有の情報を含むJSONペイロードがある
    - クライアントがAPIに送信し、APIはユーザーを識別し、ユーザー固有のアクションを実行できる
- But can’t a client just create a random JSON payload an impersonate a user?
    - JWTは署名が含まれており、署名を検証することでペイロードが改竄されていないことを担保できる
- But if I have a valid and signed JWT and someone steals it from the client, can’t they use my JWT forever?
    - 基本的にJWTを用いて、認証/認可を行う場合は他のAPIやDBには依存せず、独立して検証を行うため、盗まれたトークンかどうかを判別できない
    - そのため有効期限(exp)をを短くして、盗まれた際にもできるだけ早く無効になるように設計する
    - またCookieやlocalstrageなどでクライアント側で保存しないことも重要
- Ugh! This seems complicated. Why shouldn’t I stick to good old session tokens?
    - マイクロサービスでは各サービスで独立してクライアントから渡されたトークンを検証できる
    - またユーザー情報等取得するために、データベースにアクセスする必要もなくなる
    - モノリシックフレームワークによって発行されたセッショントークンを用いるだけで良いならJWTは必要ない
- So we’ve got the token, now where do we store this token?
    - XSS攻撃を受けやすいため、localstorageに保存すべきではない
- What about saving it in a cookie?
    - Cookieに保存することもXSSを受けやすい
        - Javascriptから読める場合は特に
    - HttpOnlyだけでなく、適切なCSRF対策が必要
    - AuthサーバーとAPIサーバーが異なるドメインでホストされていない場合、SameSite Cookie仕様により、CookieベースのアプローチはCSRF攻撃から安全になる
- The token is still valid and can be used. What if I need to ensure that the token cannot be used ever again?
    - JWTを中央集権的なDB等を持たない構造にすることが多いが、そうすると即座に特定のトークンを無効化することができない
    - jtiを識別子にトークンDBを持つ or ログアウト/無効化されたトークンのinvalid listをAPIやDBで持つことによって即座に無効化は達成できる
    - がAPIやDBに依存せず、各APIで検証が行える利点は消える  
- JWTの有効期限を短くした分、リフレッシュトークンでトークンを再度取得する
    - トークンの保存方法は上記の順でベター
        - HttpOnly Cookieに更新トークンの永続化(CSRFから安全、XSSに少し優れている)
        - HttpOnly CookieでのJWTトークンの永続化(CSRFになりやすく、XSSに少し優れている) 
        - localstorageでのJWTトークンの永続化(XSSになりやすい)
- Server side rendering (SSR)
    - How does the SSR server know if the user is logged in?
        - ブラウザは、現在のユーザーのIDに関する情報をSSRサーバーにCookieを使用して送信する
        - 認証済みページがSSRの場合、auth APIのドメインがSSRサーバーのドメインと同じである必要がある。そうしないとCookieがSSRサーバーに送信されない

DID