ログイン、リフレッシュ処理の流れ

地獄みたいなコードになってたので整理したものをまとめておきます。

正式なフローとかではないのでご了承ください。
SPAを想定しています。

データ

アクセストーク

  • JWT
  • このトークンをparseすることでuser_idを取り出し、そのユーザーからのアクセスとみなす
  • アクセスの度に取り出したuser_idからUsersテーブルを引くようなことは基本しない(はず)
  • 有効期間は短くする(1時間程度)
  • バックエンドには保存しない
  • フロントエンドにはメモリ(javascriptの変数)上に保存する

リフレッシュトーク

  • JWT
  • アクセストークンを更新するためのトーク
  • 有効期間を長くする(〜数ヶ月)
  • バックエンドではセッションIDとともにDBに保存する
  • JWT単体で無効化できない、payloadの中身が読める、サイズが大きいなどの理由でフロントには返さない
  • DBのレコードを削除することで無効化する

セッションID

  • UUID
  • DBに保存したリフレッシュトークンにアクセスするための文字列
  • バックエンドではリフレッシュトークンと一緒にDBに保存する
  • フロント側ではリフレッシュトークンの代わりにクッキー内に保存する
  • クッキーをhttpOnly属性にすることで、javascriptから読み取られない状態でフロントに保存することができる

まとめ

アクセストーク リフレッシュトーク セッションID
役割 そのユーザーになれる アクセストークンを再発行できる リフレッシュトークンの取得と削除
有効期間 短時間(~1時間) 長時間(~数ヶ月) 永続
形式 JWT JWT UUID
中身 user_idなど user_idなど -
保存場所(バックエンド) 保存しない DB DB
保存場所(フロントエンド) メモリ内 保存しない cookie(httpOnly)

流れ

サインイン

サインアップ(ID、パスワード登録)済みとします。

  1. (フロント)ID、パスワードを送信
  2. (バックエンド)ID、パスワードのチェック
  3. (バックエンド)OKならリフレッシュトークンとセッションIDを生成し、保存
  4. (バックエンド)セッションIDを乗せたクッキーをSet-Cookieでフロントに返す
  5. (アクセストークンも同時に返してセットさせるか、またはブラウザ更新して次のアクセストークン取得処理を実行させる)

アクセストークンの取得、リフレッシュ

アクセストークンはメモリ上に保存しているため、ページを離れると削除されてしまう。
ブラウザ更新時や再訪問時にはセッションIDを用いてアクセストークンを取得し直す必要がある。

  1. (フロント)リクエストを送信
  2. (バックエンド)クッキーからセッションIDを取り出し、リフレッシュトークンを検索
  3. (バックエンド)リフレッシュトークンをチェック
  4. (バックエンド)有効ならセッションIDとリフレッシュトークンを更新して再保存、アクセストークン生成
  5. (バックエンド)セッションIDをクッキーにセットし、レスポンスでアクセストークンを返す
  6. (フロント)アクセストークンをメモリに保存

アクセストークンの自動更新

ユーザーが長時間操作を行うと、途中でアクセストークンが切れて通信できなくなってしまう。
アクセストークンが切れるより前のタイミングでアクセストークンの取得、リフレッシュを再実行し、アクセストークンを更新する必要がある。
setIntervalなどを使い、定期的に実行させる。

アクセストークンによるAPI通信

  1. (フロント)バックエンドへの各リクエストに対して、アクセストークンをAuthorizationヘッダにつけて実行
  2. (バックエンド)Authorizationヘッダからアクセストークンを取り出し、検証
  3. (バックエンド)parseして得られたuser_idからのアクセスとして取り扱う

サインアウト

フロントエンドからhttpOnlyクッキーが削除できないので、バックエンド経由で削除する必要がある

  1. (フロント)アクセストークンを削除、クッキー削除リクエスト送信
  2. (バックエンド)無効なクッキー(value: "",expires: now(), max-age 0など)をセットさせる

その他

reactの場合

アクセストークンがセットされる前に認証が必要なリクエストが飛んで失敗するので、成否に関わらずアクセストークン取得処理が終わってから各ページがrenderされるようにする。

            {authFinished ? <RouterProvider router={router} /> : null}

参考URL

セキュアなトークン管理方法 - Carpe Diem
より良い記事