kanachi-blog

notionでの公開記事をastro-notion-blogを使って公開するよ

JWTを使ったTokenベース認証

認証機能

安全なWebアプリケーションの開発には不正アクセスの脅威からアカウントを守る必要があります。

その為に必要なのが、誰であるかを実証する「認証」、その権限を持つかを実証する「認可」 です。

認証: 「あなたは誰ですか?」 を確認
認可: 「あなたには、リソースにアクセスする権限がありますか?」 を確認

一般的な認証のフロー

  1. ID/パスワード入力
  2. バックエンド認証システムに対してIDに紐づくパスワードが正しいか照合する -> 認証
  3. 照合結果(ユーザーに付与されたサービス提供範囲)をWebシステムに返却する -> 認可
  4. 確認結果をもとにユーザーにサービス提供する

JWTを使ったTokenベース認証のフロー

  1. ID/パスワード入力
  2. HTTPS通信でバックエンドに対してPUTリクエストで送信
  3. バックエンド認証システムに対してIDに紐づくパスワードが正しいか照合する -> 認証
  4. ユーザ情報をもとにJWT生成をしてcokkieに保存
  5. middlewareを設定して認証ユーザ以外を弾く
  6. 照合結果(ユーザーに付与されたサービス提供範囲)をフロントに返却する -> 認可

認証フロー詳細説明

1. ID/パスワード入力

フロントエンド側の入力フォームなどでID/パスワード等認証に必要な情報を入力してもらう

2. HTTPS通信でバックエンドに対してPUTリクエストで送信

入力された認証データをフロントエンド側からバックエンドのAPIに対してPOSTリクエストを実行して平文のまま送信

平文って危なくないの?フロント→サーバ間の通信での脆弱性は?

HTTPSだと通信が暗号化されているため一定は防げる

HTTPSは完璧なの?脆弱性ほんとにないの?

これに関しては賛否あるようですが基本的にはいらないと考えてもいいかも

詳しくは下記のサイト参考

それでも怖い!フロント側で暗号化してからサーバーサイドに渡せば良くない?

HTTPSを使用するだけで十分なセキュリティを提供することができる。

ただそれでも怖い人は、公開鍵暗号化アルゴリズム(RSAなど)を使用してデータを暗号化できます。

  1. フロント側で公開鍵を使って暗号化
  2. サーバーサイドで、受け取った暗号化データを秘密鍵を使用して復号

フロント側での暗号化にbcyptとか使っちゃダメなの?

bcryptは不可逆のハッシュ関数で公開鍵方式ではないので暗号化する際に利用した暗号鍵などが流出する恐れがあります。viteなどの環境変数にも機密情報を載せる

Vite のソースコードに公開される変数は最終的にクライアントバンドルに入るので、VITE_* 変数はセンシティブな情報を含まないようにすべきです。

3. バックエンド側でIDに紐づくパスワードが正しいか照合する
  1. バックエンド側でフロントエンドから送られてきた認証データを受け取る。
  2. 認証データをハッシュ化してDBと見比べ該当するユーザが存在するか照合する
    • bcryptとか使ってハッシュ化可能

    なんでハッシュ化するの?

    DBは脆弱性の可能性が沢山!!

4. ユーザ情報をもとにJWT生成をしてcokkieに保存

該当ユーザが存在した場合JWTを生成してCokkieに保存

認証ができたので再度アクセスする度にサインインし直す手間を省くためにcokkieにJWT化したユーザ情報を保存します。これで再度アクセスが来た際にはcokkieのJWTを確認してユーザーを特定します。

Cokkieてなんぞや?

4歳児でもわかるように説明してくれる下のブログを読んでね

簡単にいうと、ウェブサイトがインターネットを通じてあなたのブラウザに一時的に情報を保存する仕組みです。ただこれには有効期限があり、サーバ側とフロント側の両方からアクセスできることを覚えておいてください。

Cokkieは分かったけどJWTって何だよ!

JSON Web Token(JWT) は、JSON ベースのデータを暗号化してつくられる文字列で、

認証や認可のための仕組みとして Web アプリケーションなどで用いられる技術です。

詳しくは以下のサイトを見てね

JWTを使うと何が嬉しいの?

生成したtokenが改竄されていないかを後から検証することができます

JWTは情報を改竄されたかを後から検証できるけど、暗号化をするわけではないから読み取られると情報は筒抜けになります。

じゃあフロント側からcokkieの中身を見られたらユーザ情報抜き取られるじゃん

そうです、抜き取られます。だからユーザ情報を入れる際にcokkieをHTTPOnly = trueにする。こうするとブラウザ側のJSからcokkieがアクセス不可になりcokkieから情報を抜かれる心配が減ります。

HttpOnly 属性を持つ Cookie は、 JavaScript の Document.cookie API にはアクセスできません。サーバーに送信されるだけです。例えば、サーバー側のセッションを持続させる Cookie は JavaScript が利用する必要はないので、 HttpOnly 属性をつけるべきです。この予防策は、クロスサイトスクリプティング (XSS) 攻撃を緩和するのに役立ちます。

じゃあHTTPonlyにしていればJWTに何でも入れていいの?

「署名付きトークンの場合、この情報は改ざんから保護されていますが、誰でも読み取ることができることに注意してください。暗号化されていない限り、JWT のペイロードまたはヘッダー要素に機密情報を含めないでください。」と公式に書いてあります。

Do note that for signed tokens this information, though protected against tampering, is readable by anyone. Do not put secret information in the payload or header elements of a JWT unless it is encrypted.

じゃあJWTとしてcokkieに何を入れればいいの

認証時には送られてきたcokkieのJWTを呼んでユーザーを一意に特定したいだけだからユーザーを識別するためのユーザーIDだけあれば最低限はいいよ。

5. ミドルウェアを設定して認証ユーザー以外を弾く

サインインが必要なエンドポイントに対してのアクセスは全てミドルウェアを設定して認証ユーザー以外を弾くようにしましょう

サインイン時にはフロントからのPOSTリクエストでユーザーを一意に特定することができたので、cokkieの有効期限がある間の再度アクセスはcokkieを確認してJWTが改竄されていないことを検証できればリクエストを通します

CSRF対策はどうすればいいの?

cokkieのSameSite属性をlaxに指定しよう!

SameSite属性がlaxだとクッキーが第三者のサイトへのリクエストには送信されないようになります。デフォルト値だとブラウザによって違うのでサーバーサイドなどで自分で設定しなおしたほうが良いです。

  1. Chrome、Firefox、Edge(バージョン80以降)などの一部のブラウザでは、SameSite属性のデフォルト値はLaxです。これは、クッキーが第三者のサイトへのリクエストには送信されないようにする制約を意味します。ただし、同じドメイン内のリクエストには送信されます。
  2. Safari(バージョン12.1以降)では、SameSite属性のデフォルト値はNoneです。これは、SameSite属性が指定されていない場合でも、クッキーがクロスサイトリクエストに対して送信されることを意味します。ただし、Secure属性(HTTPS経由でのみ送信される)が設定されている必要があります。

6. 照合結果(ユーザーに付与されたサービス提供範囲)をフロントに返却する

フロント側にユーザーに付与されたサービス提供範囲・リソースを返します。

ここではHTTPS通信を信じて平文のままリソースを返しましょう。

これで認証機能はだいたい完成しました!