JWT(JSON Web Token)認証は、クライアントとサーバー間で情報を安全にやり取りするための標準的な認証方式です。主にAPI認証やWebアプリケーションのセッション管理などに使用され、特に分散型のシステムやマイクロサービスアーキテクチャにおいて広く利用されています。
JWTは、JSONオブジェクトを用いてクレーム(claims)と呼ばれる情報を持ち、これを安全に伝えるために符号化や署名が施されています。これにより、JWTを受け取った側がそのトークンが改ざんされていないことを確認できます。
1. JWTの構造
JWTは、3つの部分から構成されるトークン形式です。JWTトークンは、次のように**ピリオド(.)**で区切られた3つの部分から成り立っています:
- ヘッダー(Header)
- ペイロード(Payload)
- 署名(Signature)
1.1 ヘッダー(Header)
ヘッダーは、トークンのタイプと署名アルゴリズムに関する情報を含んでいます。一般的に、JWTの場合は次のような形式です。
{
"alg": "HS256",
"typ": "JWT"
}
alg
: 署名に使用するアルゴリズム(例:HS256, RS256)typ
: トークンの種類(この場合は “JWT”)
1.2 ペイロード(Payload)
ペイロードには、トークンに含めたい情報(クレーム)を保持しています。クレームには、次の2種類があります:
- 登録済みクレーム(Registered Claims):標準化されたクレーム(例:
iss
(発行者)、sub
(主題)、exp
(有効期限)など) - 公開クレーム(Public Claims):アプリケーション固有のクレーム(例:ユーザーのIDやロールなど)
例:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1609459200
}
sub
: トークンの対象者name
: ユーザー名admin
: 管理者かどうかを示すフラグexp
: トークンの有効期限(UNIXタイムスタンプ)
1.3 署名(Signature)
署名は、JWTの信頼性を保証するために使用されます。署名には、ヘッダーとペイロードをハッシュ化し、それを秘密鍵または公開鍵で署名します。
署名の生成方法:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)
JWTを受け取る側は、この署名を検証することで、トークンが改ざんされていないかどうかを確認できます。
2. JWTの流れ
JWTを使った認証の一般的な流れを説明します。
2.1 ユーザーのログイン
- クライアント(例:ブラウザ)がユーザーの認証情報(例:ユーザー名とパスワード)をサーバーに送信。
- サーバーは認証情報を検証し、正しければJWTトークンを生成してクライアントに返す。
2.2 トークンの保存
クライアントは受け取ったJWTトークンを保存します。保存場所としては以下が一般的です:
- ローカルストレージ(
localStorage
) - クッキー(
HttpOnly Cookie
)
2.3 認証されたリクエストの送信
クライアントは、その後のリクエストでJWTトークンを使用して認証を行います。通常、トークンはAuthorization ヘッダーに次の形式で含められます:
Authorization: Bearer <JWTトークン>
2.4 トークンの検証
サーバー側では、受け取ったJWTトークンを次の手順で検証します:
- 署名の検証:トークンが改ざんされていないか確認します。署名は秘密鍵や公開鍵を使って確認されます。
- 有効期限の確認:トークンが有効期限内かどうか(
exp
クレームを確認)を確認します。 - トークンが有効であれば、サーバーはそのリクエストを認証されたものとして処理し、必要なデータを返します。
3. JWTの利点
JWT認証には、いくつかの利点があります。
3.1 ステートレスな認証
JWTは、サーバーがトークンの状態を保持する必要がないため、**ステートレス(stateless)**な認証方式として機能します。セッション情報をサーバー側に保持する従来のセッションベースの認証とは異なり、JWTはすべての情報をトークン自体に含むため、サーバー側にセッションを保存する必要がありません。これにより、スケーラブルなアプリケーションに適しています。
3.2 セキュリティ
JWTは、秘密鍵または公開鍵を使って署名されるため、トークンの改ざんを防ぐことができます。署名を検証することで、トークンの信頼性を保証できます。また、HTTPSと組み合わせることで、トークン自体の盗聴を防ぐことが可能です。
3.3 クライアントサイドでのデータ保持
JWTは、クライアント側にトークンを保持するため、サーバーリソースを節約できます。ログイン状態やユーザー情報をサーバー側で保持せずに済むため、分散システムやマイクロサービスアーキテクチャで特に有効です。
4. JWTの欠点とセキュリティ考慮
JWTには多くの利点がありますが、いくつかの欠点やセキュリティリスクもあります。それらを理解し、適切に対策を講じることが重要です。
4.1 トークンサイズ
JWTはペイロードに情報を含むため、トークンサイズが大きくなりがちです。これがリクエストヘッダーに含まれると、ネットワークの帯域に影響を与えることがあります。ペイロードには必要最低限の情報を含めるように設計しましょう。
4.2 トークンの漏洩
JWTトークンがクライアントに保存され、リクエストごとに送信されるため、トークンの漏洩には特に注意が必要です。漏洩した場合、第三者がトークンを使って不正アクセスを行う可能性があります。以下の対策が有効です:
- HTTPSの使用:通信の暗号化を行う。
- クッキーの
HttpOnly
属性:JavaScriptからアクセスできないように設定。 - 短い有効期限の設定:
exp
クレームを使って、トークンの有効期限を短く設定する。
4.3 トークンの無効化が難しい
JWTはサーバー側で状態を保持しないため、トークンを一度発行した後に無効化するのが難しいという課題があります。これに対する対策として、以下の方法が考えられます:
- ブラックリスト方式:無効化されたトークンをサーバー側で記録し、受信時にチェックする。
- 短い有効期限とリフレッシュトークンの組み合わせ:トークンの有効期限を短く設定し、必要に応じて新しいトークンを発行するリフレッシュトークンを使用する。
5. JWTを使った認証の実装例
最後に、簡単なJWT認証の実装例を紹介します。ここでは、PythonのFlaskとPyJWTライブラリを使ってJWT認証を実装します。
5.1 必要なライブラリのインストール
まずは必要なライブラリをインストールします。
pip install Flask PyJWT
5.2 FlaskでのJWT認証の実装
次に、Flaskを使った簡単なJWT認証APIのコードを見てみましょう。
from flask import Flask, request, jsonify
import jwt
import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
# ログインしてJWTトークンを発行
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
if data['username'] == 'user' and data['password'] == 'pass':
# 有効期限を設定
token = jwt.encode({
'user': data['username'],
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}, app.config['SECRET_KEY'], algorithm='HS256')
return jsonify({'token': token})
return jsonify({'message': '認証に失敗しました'}), 401
# 認証されたリクエストを処理
@app.route('/protected', methods=['GET'])
def protected():
token = request.headers.get('Authorization').split()[1] # "Bearer <token>"
try:
# トークンをデコードして検証
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
return jsonify({'message': f'ようこそ {data["user"]} さん!'})
except jwt.ExpiredSignatureError:
return jsonify({'message': 'トークンの有効期限が切れています'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': '無効なトークンです'}), 401
if __name__ == '__main__':
app.run(debug=True)
5.3 このコードのポイント
jwt.encode()
: トークンを発行します。exp
を使って有効期限を設定します。jwt.decode()
: トークンをデコードして、改ざんがないか検証します。トークンが無効だったり期限切れの場合、適切なエラーメッセージを返します。
まとめ
JWT認証は、Webやモバイルアプリケーションにおいて、ステートレスな認証を提供する強力な方法です。JWTはクライアントとサーバー間での情報のやり取りを効率化し、スケーラブルなアーキテクチャに適しています。ただし、トークンの保護や有効期限の設定など、セキュリティ面には十分注意する必要があります。
- ヘッダー、ペイロード、署名という3つの構成要素を持つ。
- トークンの生成と検証を通じて、安全な認証と通信が可能。
unittest
やpytest
と同様に、デバッグやテストツールと組み合わせることで、効率的な開発と運用を実現できる。
JWTは非常に柔軟で強力な認証方法なので、適切に使うことで高いセキュリティとパフォーマンスを維持できます。