🔐

JWTを理解する(Cognitoを使ったログイン機能)

に公開

JWTについて理解が不十分だったため、 本記事ではJWTの仕組みや作成〜活用方法を整理しました。
例としてCognitoを使ったログイン機能を扱います。

JWTとは

Json Web Tokenの略で、ログインした際に返却される値です。
中身は以下3つです。

トークン名 内容 用途
ID Token ユーザー情報(email, nameなど)を含む フロントで表示などに利用
Access Token ユーザーのグループ情報・権限を含む APIアクセス制御に使用
Refresh Token トークンの期限切れ時に更新に使用 サイレントリフレッシュなど

ID TokenとAccess TokenはHEADER.PAYLOAD.SIGNATURE(ヘッダ.ペーロード.署名)の形式で、Refresh TokenはJWTではなく、単なるランダム文字列です。

ID Token

ID Token にはログイン中のユーザー情報(email、nameなど)が含まれます。
ログイン済みユーザーの情報をフロントエンドで扱うために使います。
ID TokenをBase64URLでデコードすることで中身を確認できます。

ヘッダ

{
  "alg": "RS256",     // 署名アルゴリズム(RS256=公開鍵方式)
  "typ": "JWT",       // トークンの種類(通常JWT)
  "kid": "abcde12345" // 公開鍵ID(JWKSから鍵を選ぶために使う)
}

ペイロード

{
  "sub": "abcdefgh-1234-5678-xxxx-yyyyyyyyyy", // ユーザーID(一意な識別子)
  "email": "user@example.com",
  "email_verified": true,
  "name": "User Name",
  "aud": "xxxxxxxxxxxxxxxxxxxxxxxxxx",          // アプリクライアントID
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX", // 発行者(Cognito)
  "exp": 1710000000,  // 有効期限(UNIX時間)
  "iat": 1709996400   // 発行時刻(UNIX時間)
}

Access Token

Access TokenはAPI実行時の認可・ユーザー特定に使われます。
フロントエンドからAPIを呼ぶとき、Authorizationヘッダにセットして送信し、バックエンドでデコードされます。
ID Tokenと同様にBase64URLでデコードすることで中身を確認できます。

{
  "alg": "RS256",     // 署名アルゴリズム
  "typ": "JWT",       // トークンの種類
  "kid": "abcde12345" // 公開鍵ID
}

Payload

{
  "sub": "abcdefgh-1234-5678-xxxx-yyyyyyyyyy", // ユーザーID
  "username": "user123",                       // Cognito上のユーザー名
  "scope": "email openid profile",             // 許可されたスコープ
  "auth_time": 1709990000,                     // 認証完了した時刻
  "exp": 1710000000,                           // 有効期限
  "iat": 1709996400,                           // 発行時刻
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX",
  "token_use": "access",                       // これはAccess Tokenだと示す
  "client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxx"    // アプリクライアントID
}

JWTのそれぞれの部分の使用タイミングと役割は以下のとおりです。

JWTの部分 使用タイミング バックエンドでの役割と使い方
Header 検証前(最初) - alg(署名アルゴリズム)を確認
- kid(鍵のID)でJWKSから正しい公開鍵を選ぶ
Payload 検証後 - sub(ユーザーID)でユーザー特定
- scopegroup で認可判断
- exp で期限切れ確認
Signature 検証時 - HEADER + PAYLOAD を元にハッシュを生成し、署名と一致するかチェック(改ざんされてないか確認)

Refresh Token

Refresh Token は、Access Token や ID Token の 有効期限が切れたときに新しいトークンを取得するために使います。
通常、ログイン時に一緒に返却され、有効期限は数日〜数週間と長めに設定されています。
Refresh Token はJWTではなく、ランダムな文字列形式です。
そのためデコードはできず、Cognitoだけがこのトークンを理解・検証できます。

JWTの作成と使い方

JWTの作成

  1. ユーザーは、新規登録または登録済みのユーザーIDとパスワードでアプリケーションにログインする
  2. アプリケーションは、ユーザーIDとパスワードが正しいかどうかをデータベースと照らし合わせて確認する
  3. 2が問題なければ、アプリケーションはJWTを作成する
  • JWTの署名には、アプリケーションが保持している秘密鍵を使う
  • 鍵の作成には共通鍵方式と公開鍵方式があるがcognitoは公開鍵方式)
  1. アプリケーションは、作成したJWTをユーザーに返す
  2. JWTを受け取ったユーザーは、ブラウザのCookieやセッションストレージに保存する

画面にユーザ情報を表示する

  1. ID Tokenをデコードして、email/nameなどを取得する
  2. 画面に表示する

APIを呼び出す

  1. Access TokenをAuthorizationヘッダに付与して送信する

  2. JWT検証・認可を実施する

  • トークンが改ざんされていないか、公開鍵で検証する
  • トークンの有効期限チェック
    exp(有効期限)が現在時刻より未来かどうかをチェックし、期限切れなら401エラーを返す

有効期限内の場合

  • subを使ってユーザーの特定をする
  • scopeやgroupで、ユーザーにこのAPIを使う権限の有無を判定する
  • 問題なければAPIを実行し、権限がなければ401エラーを返す。

有効期限切れの場合

  • ユーザーに401エラーを返す
  • Refresh Tokenで新しいAccess Tokenを生成する
  • Refresh TokenでCognitoにAccess Tokenの再発行を要求する
  • 新しいAccess Tokenを受け取る
  • APIを再度呼び出す

Refresh Tokenの有効期限も切れている場合

  • ユーザーは再ログインして、新しいID Token、Access Token、Refresh Tokenを受け取る

expの必要性

expの有効期限が切れていたとしても、Refresh Tokenによる期限更新により、ユーザーは再ログインなしでAPIを実行できます。
そうするとexpが不要に思えるかもしれません。
しかし、下記理由によりexpは必要です。

トークンの使い回しを防ぐ

トークン(ID Token、Access Token)が盗まれても、有効期限が短いほど被害が最小化されます。
1年有効なトークンが漏れると「1年間ずっと不正利用できる」ことになります。

強制ログアウトや権限変更を即座に反映する

ユーザーの権限や所属グループが変更された場合、「有効期限内の古いトークン」は新しい状態を反映できません。
短い有効期限(たとえば1時間ごとにトークンが新発行される)なら、最新の情報がトークンに反映されやすくなります。

再ログインの回数を減らせる

Refresh Tokenにも有効期限があります。
expをなくして、Refresh Tokenの有効期間を短くすると、Refresh Tokenの有効期限が切れるたびに再ログインしなくてはなりません。

まとめ

トークン名 内容 用途
ID Token ユーザー情報(email, nameなど)を含む フロントで表示などに利用
Access Token ユーザーのグループ情報・権限を含む APIアクセス制御に使用
Refresh Token トークンの期限切れ時に更新に使用 サイレントリフレッシュなど
  • ID TokenとAccess TokenのexpとRefresh Tokenはどちらもトークンの有効期限を示し、Refresh Tokenの有効期限が切れるまでは、ユーザーは再ログインなしでAPIを実行できる
  • expを短め(数時間)、Refresh Tokenを長め(数日〜数週間)に設定しておくことで、ユーザ情報のトークンへの反映をスムーズに行い、セキュリティも担保できる

参考

以下を参考にさせていただきました。
https://zenn.dev/mikakane/articles/tutorial_for_jwt
https://qiita.com/asagohan2301/items/cef8bcb969fef9064a5c#jwtの検証

Discussion