🗝️

セッション認証・トークン認証と周辺知識を簡単にまとめた

2024/03/29に公開

更新履歴
・24/03/29  投稿
・同日  ログアウト時の挙動を追記

webAPIを使ったwebアプリケーション(SPA)のセキュリティ周りの学習で色々調べたので、忘れないうちに書き留める。完全に自分用。間違い指摘のコメント頂けたらびっりするほどユートピアして喜びます。

セッション認証とトークン認証の話

簡単な説明

◾️セッション認証

サーバにあるユーザ情報と紐づいたセッションIDというランダムな文字列を利用する認証方法。
クライアントのリクエストのヘッダに付与されたセッションIDに紐づくユーザ情報をサーバが認識して、どのユーザがアクセスしてきたのかを確認する。サーバはセッションIDとユーザ情報の答え合わせとして、セッションIDを保持しておかないといけないため、ステートフル(状態がある)な通信と呼ばれる。

◾️トークン認証

ユーザがログインした際に、認可サーバ(APIを実行しているサーバとは別)から発行されるユーザがアクセス可能なリソースのスコープ情報を持った文字列を利用した認証方法。クライアントからのリクエストヘッダに付加されたアクセストークンから、サーバはリクエスト元が持っている権限(アクセスできるリソース)を確認する。トークンを発行するのは認可サーバで、正当性はアクセストークン自体が担保するBearerが主流(後で説明)。トークン単体で正当性が担保できるため答えとなる情報が不要で、サーバはトークンを保持する必要がない。そのためステートレス(状態がない)な通信と呼ばれる。

基本的な処理の流れ

◾️セッション認証

  1. ユーザ認証(ログイン)後、サーバからセッションIDが発行され、サーバに保持される。
  2. セッションIDはログイン等のレスポンスでクライアントに渡され、Cookie(またはローカルストレージなど)に保存される。
  3. クライアントはリクエスト送信時、保持していたセッションIDをヘッダに付与する。
  4. サーバはリクエストに付属していたセッションIDと紐づくユーザ情報があること、有効期限が切れていないことを確認する。
  5. 紐づくユーザ情報から、ユーザがアクセス可能なリソースをクライアントに返す。

◾️トークン認証(Bearer Token)

  1. ユーザ認証(ログイン)後、認可サーバからアクセストークンが発行される。
  2. アクセストークンはログイン等のレスポンスでクライアントに渡され、Cookie(またはローカルストレージなど)に保存される。
  3. クライアントはリクエスト送信時、保持していたアクセストークンをヘッダに付与する。
  4. サーバは受け取ったアクセストークンが改ざんされていないこと、有効期限が切れていないことを確認する。
  5. 含まれるスコープ情報から、アクセス可能なリソースをクライアントに返す。

ログアウト時の挙動

◾️セッション認証

ユーザ情報とセッションIDの紐付けはサーバ側で管理しているため、ログアウト時にはサーバ上で紐付けを解除する。

◾️トークン認証

アクセストークンはユーザがアクセス可能なサーバリソースのスコープにのみフィーチャーしたデータなので、ユーザ情報と紐づかず、更にクライアントのみが保持するためサーバからは操作できない。そのため、XSSなどでトークンを盗まれた場合、ユーザがログアウトしたとしても有効期限が来ない限り不正アクセスが可能となる。対策としては、アクセストークンの有効期限を短く設定することが挙げられるが、そうした場合短いスパンでユーザ認証(ログイン)が求められることとなり、ユーザビリティが低下してしまう。その対策として、リフレッシュトークン(後述)を利用することで、ユーザビリティとセキュリティのバランスが保たれている。

注目するべき特徴

◾️セッション認証

  • セッションIDをサーバが保持しないといけない。
  • ステートフルであるためMPAなどの認証に採用されやすい。(必ずでは無い)

◾️トークン認証

  • アプリサーバとは別に認可サーバが必要になる。
  • クライアントのみがアクセストークンを保持している。
  • ステートレスであるためRESTfulAPIの認証に採用されやすい。(必ずでは無い)
  • セッション認証よりセキュアとされる。

トークン認証がよりセキュアな理由

  1. セッションIDに比べてアクセストークンの有効期限が短い
  2. リフレッシュトークンを用いてアクセストークンを生成している
  3. Bearer Tokenの仕組み

◾️リフレッシュトークンを用いたアクセストークンの生成

トークン認証に単一のアクセストークンを用いることは少なく、一般にはアクセストークンとリフレッシュトークンの2つが利用される。リフレッシュトークンはアクセストークンの有効期限が切れた際にアクセストークンを再度発行するためのトークンで、これもクライアントが保持する。

生成~の流れ

  1. ユーザ認証(ログイン)後、認可サーバからアクセストークンとリフレッシュトークンが発行される。
  2. トークンはレスポンスでクライアントに渡され、Cookie(またはローカルストレージなど)に保存される。
  3. クライアントはリクエスト送信時、ヘッダにアクセストークンを付与する。
  4. サーバは受け取ったアクセストークンの有効期限が切れていることを確認する。
  5. クライアントに有効期限切れのレスポンスを送る。
  6. クライアントは認可サーバにアクセストークン生成のリクエストを送る。
  7. リクエストヘッダにはリフレッシュトークンが付与される。
  8. リフレッシュトークンの正当性・有効期限を確認した認可サーバはアクセストークンを発行する。
  9. クライアントは新しく発行されたアクセストークンをリクエストヘッダに付与してサーバにリクエストを送る。

実際のところ、トークン認証はアクセストークンではなくリフレッシュトークンの有効期限に依存することとなり、もしアクセストークンが流出したとしても、短い有効期限のおかげで被害を最小限に抑えることができる。この仕組みではリフレッシュトークンの重要性が高まるが、リフレッシュトークンを用いた通信はアクセストークン生成のリクエストのみであり、その通信回数はアクセストークンやセッションIDを用いたものより少なくなり、相対的に盗まれるリスクが低くなる。
ただ、トークン認証は認可サーバという認証のためのサーバが必要になるのでコストはかかる。

◾️Bearer Tokenの仕組み

一般的なトークンは、リクエストを受け取ったサーバがトークンを発行した認可サーバに問い合わせることでその正当性を確かめるのだが、Bearer Tokenはトークンの正当性の確認がサーバ内で完結する。

広く使われているトークンの種類であるJWT(JSON Web Token)を例に挙げてみる。
JWT単体ではただのトークンとさして代わりないのだが、JWTの仕様であるJWS(JSON Web Signature)JWE(JSON Web Encryption)がセキュリティをより強固にしている。

名前 説明
JWS JWTのデジタル署名。ヘッダ + JWT + デジタル署名(ヘッダ+JWTをクライアントの秘密鍵で暗号化したもの)をアクセストークンとして扱う。サーバはデジタル署名を公開鍵で復元し、ヘッダ+JWTと比べることで改ざんを確認する。
JWE JWTの認証付き暗号化。暗号化されたJWT + 暗号化に使った共通鍵 + 暗号化アルゴリズム + 暗号化に使われるベクトル(パラメータ) + 復元した暗号文の整合性を確認する認証が使われる。

これらの方法で秘匿性が高められたトークンをサーバ内でデコード・デクリプションすることで、トークン自身の正当性を確認する。

周辺知識

クライアントでの認証情報の管理場所

認証情報などの秘匿性が高い必要のある情報には様々な管理方法があるが、一般的にはブラウザのCookieで管理するのが主流である。Cookieが利用される理由としては、リクエストに必要な認証情報がCookieにある場合は自動で付与してくれるというブラウザの仕様や、同じように認証情報の保存先として名前が挙がるLocal Storageよりも安全であるなどが挙げられる。その他にはブラウザメモリが使用されることがあるが、勉強中...

なぜLocal StorageはCookieに劣るのか

Local Storageは特にXSS(Cross-Site Scripting)に弱いとされる。XSS自体を受けるというリスクはCookieと同じなのだが、どちらがマシかと言われるとCookieに軍配があがる。
どういうことかというと、Cookieに認証情報を保存した場合、前述の通りブラウザが認証情報を自動でリクエストに乗せてくれる。しかし、Local Storageに認証情報を保存した場合、認証情報をリクエストのヘッダに入れるようフロント側でコーディングする必要がある。もしここでサードパーティライブラリの脆弱性が存在した場合、コード上で直接認証情報を扱っている以上それらを盗まれてしまう可能性がある。
つまり、Cookieに保存すると、XSSは通っても認証情報を盗まれることはないが、Local Storageに保存した場合は認証情報そのものが割れてしまう。
攻撃の被害を最小限にするならやはりCookieになる。

そのほかには、キャッシュを消すとLocal Storageの中身も消えちゃうとか完全にクライアントサイドだからサーバー側からデータを操作できないとかも問題視されてるらしい。
しかし、Cookieには1つのデータの容量が4KBという縛りが存在するため、容量の大きなトークン認証を採用する際に、あえてCookieを使わないという事例もある。

ちなみに以上はwebアプリの話で、そもそもCookieが存在しないNativeアプリにはOSごとに機密性の高い情報を格納するストレージがあって、そこは安全だからこの話であんまり出てこない、、、気がする。

関連する主流な攻撃

XSS(Cross-Site Scripting)

SNSのプロフィールや掲示板サイトなどに悪意のあるスクリプト(例:Cookieを盗み出すAPI実行するもの)を入力し、開いたり実行したりなどのイベントが発生すると、アプリに誤作動を生じさせたり、個人情報を盗み出したりする攻撃。

CSRF(Cross-Site Request Forgery)

会員制などのwebアプリケーションのユーザがそのサービスにログインし、認証情報がCookieに残っている場合に受ける可能性がある攻撃。ブラウザがリクエストに対してCookieの認証情報を自動付与する仕様を悪用して、入力完了ボタンや別のURLに偽装したそのサービスの退会・送金・購入などのAPIを攻撃者のwebページに配置し、意図せずユーザがAPIを発火させる。

CSRFの対策

CSRFトークンと呼ばれるランダムな文字列を用いることが一般的。

  1. クライアントからのリクエストに対して、サーバはCSRFトークンを発行し、サーバで保持する。
  2. クライアントはレスポンスに付属しているトークンを、HTML内に保持する。
  3. クライアントはHTMLに保持したトークンをリクエストに付与して送る。
  4. サーバはリクエストに付属したCSRFトークンとサーバが保持しているCSRFトークンを比較することで不正アクセスではないことを確認する。

ここで大事なのは、2.で、リクエストに付与するトークンは、そのリクエストを実行したHTMLに依存しているので、そのHTML外からのアクセスは正しいトークンを付与できない、という点。。。

トークンはリクエストごとに更新されることが一般的ではあるが、アクセストークンのように有効期限のある固定のトークンでも良い。ただし、その場合はCSRFトークンを守るための対策が必要になる。


OAuth2.0や、挙げた言葉の深掘りだったりも勉強したいので、"完全に理解した"次第追記します。

Discussion