Flask-JWT-Extended完全ガイド:Python Flaskアプリの認証を強化 🔐

Web開発

はじめに:Flask-JWT-Extendedとは?🤔

Webアプリケーション開発において、ユーザー認証は不可欠な要素です。特にAPIベースのアプリケーションでは、ステートレスな認証メカニズムが求められることが多く、その代表的な手法としてJSON Web Token (JWT) が広く利用されています。

JWTは、認証情報をJSONオブジェクトとして表現し、署名または暗号化によって改ざんを防ぐことができるコンパクトで自己完結したトークン形式です。サーバーはセッション情報を保持する必要がなく、トークン自体に必要な情報が含まれているため、スケーラビリティに優れています。

Pythonの人気WebフレームワークであるFlaskでJWT認証を実装する際、非常に強力で柔軟な選択肢となるのが Flask-JWT-Extended ライブラリです。このライブラリは、JWTの生成、検証、管理を容易にし、開発者が認証ロジックの実装に集中できるようサポートします。

Flask-JWT-Extended の主な利点は以下の通りです:

  • ✅ アクセストークンとリフレッシュトークンの分離
  • ✅ トークンの有効期限設定
  • ✅ ルート保護のためのデコレータ
  • ✅ カスタムクレーム(ペイロードへの追加情報)のサポート
  • ✅ トークンの無効化(ブロックリスト/旧ブラックリスト)機能
  • ✅ CSRF対策のサポート
  • ✅ トークン送信場所(ヘッダー、Cookie、JSONボディなど)の柔軟な設定
  • ✅ 詳細なエラーハンドリング

このブログ記事では、Flask-JWT-Extended の基本的な使い方から高度な機能まで、具体的なコード例を交えながら詳しく解説していきます。Flaskアプリケーションに堅牢な認証機能を追加したい方は、ぜひ参考にしてください。

🚀 インストールと基本的なセットアップ

まず、Flask-JWT-Extended をプロジェクトにインストールしましょう。pipを使用するのが一般的です。

pip install Flask-JWT-Extended

次に、Flaskアプリケーションで Flask-JWT-Extended を初期化し、基本的な設定を行います。最も重要な設定は JWT_SECRET_KEY です。これは、トークンの署名に使用される秘密鍵であり、推測困難な安全な文字列を設定する必要があります。

以下は、最小限のFlaskアプリケーションでのセットアップ例です。

from flask import Flask
from flask_jwt_extended import JWTManager
import os

app = Flask(__name__)

# JWTの署名に使用する秘密鍵を設定
# 環境変数から読み込むか、安全な場所に保管することを強く推奨
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev") # 本番環境では必ず安全なキーを設定してください!

# JWTManagerインスタンスを作成し、Flaskアプリに紐付ける
jwt = JWTManager(app)

@app.route('/')
def home():
    return "Welcome to the Flask-JWT-Extended example!"

if __name__ == '__main__':
    # 環境変数 FLASK_RUN_PORT などでポートを指定可能
    app.run(debug=True)
注意: JWT_SECRET_KEY は絶対に公開しないでください。上記コードの "super-secret-key-for-dev" は開発用の仮の値であり、本番環境では環境変数や設定ファイル管理ツール(例:Vault, AWS Secrets Manager)などを使用して、安全に管理された強力な秘密鍵を設定する必要があります。

これで、Flaskアプリケーションで Flask-JWT-Extended を使用する準備が整いました。次に、トークンの生成と利用方法を見ていきましょう。

🔑 アクセストークンとリフレッシュトークンの生成・利用

Flask-JWT-Extended では、主に2種類のトークンを扱います。

  • アクセストークン (Access Token): APIエンドポイントへのアクセスを許可するために使用されます。通常、有効期間は短く設定されます(例: 15分)。
  • リフレッシュトークン (Refresh Token): 新しいアクセストークンを取得するために使用されます。アクセストークンよりも有効期間が長く設定されます(例: 30日)。リフレッシュトークン自体は、保護されたリソースへのアクセスには直接使用できません。

これらのトークンは、ユーザーがログインに成功した際に生成されます。トークンには、ユーザーを識別するための情報(Identity)が含まれます。

トークンの生成

create_access_token()create_refresh_token() 関数を使用して、それぞれアクセストークンとリフレッシュトークンを生成します。これらの関数には、トークンに含めるユーザー識別子(Identity)を引数として渡します。Identityには、ユーザーID、ユーザー名、メールアドレスなど、一意な情報を使用できます。

from flask import Flask, request, jsonify
from flask_jwt_extended import create_access_token, create_refresh_token, JWTManager
import os

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev")
jwt = JWTManager(app)

# ダミーのユーザーデータベース(通常はデータベースから取得)
users_db = {
    "user1": {"password": "password123"}
}

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username', None)
    password = request.json.get('password', None)

    # ユーザー認証(実際にはパスワードハッシュを比較)
    user = users_db.get(username)
    if not user or user["password"] != password:
        return jsonify({"msg": "Bad username or password"}), 401 # Unauthorized

    # Identity を指定してトークンを生成
    # Identity にはシリアライズ可能な任意のデータを指定できる(ここでは username)
    access_token = create_access_token(identity=username)
    refresh_token = create_refresh_token(identity=username)

    return jsonify(access_token=access_token, refresh_token=refresh_token)

if __name__ == '__main__':
    app.run(debug=True)

上記の例では、/login エンドポイントでユーザー名とパスワードを受け取り、簡単な検証(実際にはパスワードハッシュを比較すべき)を行った後、認証に成功すればアクセストークンとリフレッシュトークンを生成してJSONレスポンスで返しています。Identityとしてユーザー名(`username`)を使用しています。

ルートの保護とIdentityの取得

特定のルート(エンドポイント)へのアクセスを認証済みユーザーのみに制限するには、@jwt_required() デコレータを使用します。このデコレータが付与されたルートにアクセスする際、クライアントはリクエストヘッダー(デフォルトでは `Authorization: Bearer <access_token>`)に有効なアクセストークンを含める必要があります。

保護されたルート内では、get_jwt_identity() 関数を使用して、トークンに含まれるIdentity(ログイン時に指定したもの)を取得できます。

from flask import Flask, jsonify
from flask_jwt_extended import create_access_token, JWTManager, jwt_required, get_jwt_identity
import os

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev")
jwt = JWTManager(app)

# ... (login エンドポイントは省略) ...

@app.route('/protected', methods=['GET'])
@jwt_required() # このデコレータでルートを保護
def protected():
    # 現在のユーザーの Identity を取得
    current_user_identity = get_jwt_identity()
    # Identity を使ってデータベースからユーザー情報を取得するなどの処理が可能
    return jsonify(logged_in_as=current_user_identity), 200

if __name__ == '__main__':
    app.run(debug=True)

この /protected エンドポイントにアクセスするには、有効なアクセストークンを `Authorization: Bearer <token>` ヘッダーに含めてリクエストする必要があります。トークンが無効または存在しない場合は、Flask-JWT-Extended が自動的にエラーレスポンス(デフォルトでは 401 Unauthorized)を返します。

有効なトークンが提供された場合、get_jwt_identity() でIdentity(この例ではユーザー名)を取得し、それを利用してユーザー固有の処理を行うことができます。

🔄 トークンのリフレッシュ

アクセストークンはセキュリティ上の理由から有効期間を短く設定することが推奨されます(例: 15分)。しかし、ユーザーが頻繁に再ログインする必要があるのは不便です。そこでリフレッシュトークンの出番です。

リフレッシュトークンは、アクセストークンよりも長い有効期間(例: 数日、数週間)を持ちます。クライアントは、アクセストークンの有効期限が切れた後、このリフレッシュトークンを使用して新しいアクセストークンをサーバーに要求できます。これにより、ユーザーはセッションが継続しているかのようにサービスを利用し続けることができます。

リフレッシュトークンを使用して新しいアクセストークンを発行するためのエンドポイントを作成します。このエンドポイントは、@jwt_required(refresh=True) デコレータで保護します。このデコレータは、リクエストに有効なリフレッシュトークンが含まれていることを要求します(アクセストークンではアクセスできません)。

from flask import Flask, jsonify, request
from flask_jwt_extended import (
    create_access_token, JWTManager, jwt_required, get_jwt_identity,
    create_refresh_token # create_refresh_token もインポート
)
import os

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev")
# アクセストークンの有効期間を短く設定(例: 15分)
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(minutes=15)
# リフレッシュトークンの有効期間を長く設定(例: 30日)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
jwt = JWTManager(app)

# ... (login, protected エンドポイントは省略) ...

@app.route('/refresh', methods=['POST'])
@jwt_required(refresh=True) # リフレッシュトークンが必要なエンドポイント
def refresh():
    # リフレッシュトークンから Identity を取得
    current_user_identity = get_jwt_identity()
    # 新しいアクセストークンを生成
    new_access_token = create_access_token(identity=current_user_identity)
    return jsonify(access_token=new_access_token)

if __name__ == '__main__':
    # timedelta を使うためにインポート
    from datetime import timedelta
    app.run(debug=True)

クライアント側の実装は次のようになります:

  1. 最初に /login でアクセストークンとリフレッシュトークンを取得し、安全な場所に保存します(例: `localStorage`, `sessionStorage`, またはメモリ内。`localStorage` はXSSのリスクがあるため注意が必要)。
  2. 保護されたAPI(例: /protected)にアクセスする際は、アクセストークンを `Authorization` ヘッダーに付与します。
  3. APIから 401 Unauthorized エラー(またはトークン期限切れを示す特定のエラー)が返ってきた場合、アクセストークンが期限切れである可能性が高いと判断します。
  4. 保存しておいたリフレッシュトークンを使用して /refresh エンドポイントにリクエストを送信します。
  5. /refresh から新しいアクセストークンが返ってきたら、それを保存し、元のAPIリクエストを再試行します。
  6. /refresh でもエラーが返ってきた場合(例: リフレッシュトークンも期限切れ、または無効化されている)、ユーザーに再ログインを促します。

この仕組みにより、ユーザーエクスペリエンスを損なうことなく、アクセストークンの有効期間を短く保つことができ、セキュリティが向上します。✨

💡 高度な機能

Flask-JWT-Extended は、基本的なトークン管理機能に加えて、より複雑な要件に対応するための高度な機能も提供しています。

カスタムクレームの追加

JWTのペイロード(トークンに含まれる情報)には、標準的なクレーム(`iss`, `sub`, `exp`, `iat`, `jti`, `nbf` など)以外に、アプリケーション固有の情報を追加できます。これをカスタムクレームと呼びます。

例えば、ユーザーのロール(役割、例: ‘admin’, ‘user’)や権限などの情報をトークンに含めることで、APIアクセス時に毎回データベースを検索する手間を省くことができます。

Flask-JWT-Extended では、@jwt.user_claims_loader(バージョン4.x以前)または @jwt.additional_claims_loader(バージョン4.x以降)デコレータを使用して、トークン生成時にカスタムクレームを追加する関数を登録します。この関数は Identity を引数として受け取り、カスタムクレームを含む辞書を返す必要があります。

from flask import Flask, request, jsonify
from flask_jwt_extended import (
    create_access_token, JWTManager, jwt_required, get_jwt_identity,
    get_jwt # get_jwt をインポート (バージョン4.x以降)
)
import os
from datetime import timedelta

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev")
jwt = JWTManager(app)

# ダミーのユーザー情報(ロールを含む)
users_info = {
    "user1": {"password": "password123", "roles": ["user"]},
    "admin1": {"password": "adminpass", "roles": ["user", "admin"]}
}

# トークン生成時に追加のクレームを定義する関数 (v4.x以降)
@jwt.additional_claims_loader
def add_claims_to_jwt(identity):
    # identity (この例では username) に基づいてユーザー情報を取得
    user = users_info.get(identity)
    if user:
        return {"roles": user["roles"]}
    return {} # ユーザーが見つからない場合は空の辞書

# (旧バージョン v3.x 用: @jwt.user_claims_loader)
# @jwt.user_claims_loader
# def add_claims_to_access_token(identity):
#     user = users_info.get(identity)
#     if user:
#         return {"roles": user["roles"]}
#     return {}

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username', None)
    password = request.json.get('password', None)
    user = users_info.get(username)
    if not user or user["password"] != password:
        return jsonify({"msg": "Bad username or password"}), 401

    # アクセストークン生成時に add_claims_to_jwt が呼ばれ、roles が追加される
    access_token = create_access_token(identity=username)
    return jsonify(access_token=access_token)

@app.route('/admin-only', methods=['GET'])
@jwt_required()
def admin_only():
    # トークン全体 (ヘッダーとペイロード) を取得
    jwt_data = get_jwt()
    claims = jwt_data # v4.x では get_jwt() の戻り値がペイロードそのもの
    # (旧バージョン v3.x 用: claims = get_jwt_claims())

    if "admin" not in claims.get("roles", []):
        return jsonify({"msg": "Administration rights required"}), 403 # Forbidden

    current_user_identity = get_jwt_identity()
    return jsonify(message=f"Welcome Admin {current_user_identity}!", claims=claims)

if __name__ == '__main__':
    app.run(debug=True)

上記の例では、ログイン時に生成されるアクセストークンに `roles` というカスタムクレームが追加されます。/admin-only エンドポイントでは、get_jwt() (v4.x) または get_jwt_claims() (v3.x) を使ってトークンのペイロード(クレーム)を取得し、`roles` に ‘admin’ が含まれているかをチェックしてアクセス制御を行っています。

バージョンによる違い: Flask-JWT-Extended バージョン4.xから、カスタムクレームの扱い方が変更されました。@jwt.user_claims_loader@jwt.additional_claims_loader に、get_jwt_claims()get_jwt() に置き換えられています。最新のドキュメントを確認することをお勧めします。

トークンの無効化(ブロックリスト)

JWTはステートレスであるため、一度発行されたトークンは有効期限が切れるまで有効です。しかし、ユーザーがログアウトした場合や、パスワードを変更した場合、あるいはトークンが漏洩した場合など、特定のトークンを有効期限前に無効化したい場合があります。

Flask-JWT-Extended では、このためのブロックリスト (Blocklist) 機能(旧称: ブラックリスト)を提供しています。

ブロックリスト機能を有効にするには、設定が必要です。

app.config["JWT_BLACKLIST_ENABLED"] = True # バージョンによってキー名が異なる可能性あり (blocklist)
app.config["JWT_BLACKLIST_TOKEN_CHECKS"] = ["access", "refresh"] # チェックするトークンの種類

次に、@jwt.token_in_blocklist_loader デコレータ(または旧 `@jwt.token_in_blacklist_loader`)を使用して、受け取ったトークンがブロックリストに含まれているかどうかをチェックする関数を登録します。この関数は、トークンのペイロード(JWTヘッダーとデコードされたペイロードの辞書)を引数として受け取り、トークンが無効(ブロックリスト入り)であれば `True` を、有効であれば `False` を返します。

無効化されたトークンの情報(通常は `jti` クレーム:JWT ID)をどこかに保存しておく必要があります。これは、Redisのようなインメモリデータベースや、通常のデータベーステーブルなどが考えられます。

from flask import Flask, request, jsonify
from flask_jwt_extended import (
    create_access_token, create_refresh_token, JWTManager, jwt_required,
    get_jwt_identity, get_jwt
)
import os
from datetime import timedelta, timezone, datetime

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev")
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(minutes=15)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)

# ブロックリスト設定 (キー名はバージョンにより異なる可能性あり)
app.config["JWT_BLOCKLIST_ENABLED"] = True
app.config["JWT_BLOCKLIST_TOKEN_CHECKS"] = ["access", "refresh"]

jwt = JWTManager(app)

# --- ブロックリストの実装 (例: メモリ内セットを使用) ---
# 本番環境ではRedisやデータベースを使用してください
blocklist = set()
# ---------------------------------------------------

# トークンがブロックリストにあるかチェックするコールバック関数
@jwt.token_in_blocklist_loader
def check_if_token_revoked(jwt_header, jwt_payload: dict) -> bool:
    jti = jwt_payload["jti"]
    # jti がブロックリストセットに含まれているか確認
    token_in_blocklist = jti in blocklist
    return token_in_blocklist

# ... (login, refresh エンドポイントは省略) ...

@app.route('/logout', methods=['DELETE'])
@jwt_required() # ログアウトには有効なアクセストークンが必要
def logout():
    jwt_data = get_jwt()
    jti = jwt_data["jti"]
    # アクセストークンのJTIをブロックリストに追加
    blocklist.add(jti)
    # 必要であれば、関連するリフレッシュトークンも無効化するロジックを追加
    # (例: リフレッシュトークンのjtiも取得してブロックリストに追加)
    # refresh_token_jti = ... # リフレッシュトークンからjtiを取得する方法が必要
    # blocklist.add(refresh_token_jti)
    return jsonify(msg="Access token revoked")

@app.route('/logout_refresh', methods=['DELETE'])
@jwt_required(refresh=True) # リフレッシュトークンでログアウト
def logout_refresh():
    jwt_data = get_jwt()
    jti = jwt_data["jti"]
    # リフレッシュトークンのJTIをブロックリストに追加
    blocklist.add(jti)
    return jsonify(msg="Refresh token revoked")


@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
    # このエンドポイントにアクセスする前に check_if_token_revoked が呼ばれる
    current_user_identity = get_jwt_identity()
    return jsonify(logged_in_as=current_user_identity)

if __name__ == '__main__':
    app.run(debug=True)

注意: 上記のブロックリスト実装は、デモンストレーションのためにPythonの `set` を使用していますが、これはアプリケーションの再起動で失われます。本番環境では、Redis、Memcached、データベースなどの永続的なストレージを使用してください。 また、ログアウト時にアクセストークンだけでなく、関連するリフレッシュトークンも無効化する(ブロックリストに追加する)ことが推奨されます。そのためには、リフレッシュトークンの `jti` もクライアント側で保持するか、サーバー側で関連付けておく必要があります。

ログアウトエンドポイント (/logout, /logout_refresh) では、受け取ったトークンの `jti` を取得し、それをブロックリストに追加しています。@jwt_required()@jwt_required(refresh=True) で保護されたエンドポイントにアクセスがあると、Flask-JWT-Extended は自動的に check_if_token_revoked 関数を呼び出し、トークンがブロックリストに含まれていないかを確認します。含まれていれば、アクセスは拒否されます。

ロケーションのカスタマイズ

デフォルトでは、Flask-JWT-Extended は `Authorization: Bearer <token>` ヘッダーからJWTを探します。しかし、設定を変更することで、他の場所からトークンを読み取るようにカスタマイズできます。

設定キー JWT_TOKEN_LOCATION を使用します。値はリストで、複数の場所を指定できます。指定可能な場所は以下の通りです。

  • 'headers': HTTPヘッダー (デフォルト)
  • 'cookies': Cookie
  • 'query_string': URLクエリパラメータ
  • 'json': リクエストJSONボディ
# 例: ヘッダーとクッキーからトークンを探す
app.config["JWT_TOKEN_LOCATION"] = ["headers", "cookies"]

# クッキーから読み取る場合、クッキー名も設定可能
app.config["JWT_ACCESS_COOKIE_NAME"] = "access_token_cookie"
app.config["JWT_REFRESH_COOKIE_NAME"] = "refresh_token_cookie"
# セキュリティのため、CSRF対策も有効にすることが強く推奨される
app.config["JWT_COOKIE_CSRF_PROTECT"] = True
# CSRFトークンをヘッダーで受け取る場合の名前
app.config["JWT_CSRF_HEADER_NAME"] = "X-CSRF-TOKEN"

Cookieを使用する場合、セキュリティ上の配慮が必要です。特に、CSRF (Cross-Site Request Forgery) 攻撃に対する対策が重要になります。Flask-JWT-Extended はCSRF保護機能も提供しており、JWT_COOKIE_CSRF_PROTECT = True を設定することで有効になります。この場合、Cookieと一緒に送信されるCSRFトークン(通常は別のヘッダーで送信)も検証されます。

また、ログイン時にCookieにトークンを設定するには、set_access_cookiesset_refresh_cookies 関数を使用し、ログアウト時には unset_jwt_cookies 関数を使用してCookieを削除します。

from flask import Flask, request, jsonify
from flask_jwt_extended import (
    create_access_token, create_refresh_token, JWTManager, jwt_required,
    set_access_cookies, set_refresh_cookies, unset_jwt_cookies,
    # CSRF保護付きのルートには csrf_protect が必要になる場合がある
    # jwt_required(locations=['cookies']) などで指定することも可能
)
import os
from datetime import timedelta

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev")
app.config["JWT_TOKEN_LOCATION"] = ["cookies"] # トークンをCookieからのみ読み取る
app.config["JWT_COOKIE_SECURE"] = False # 開発用にFalse、本番ではTrue (HTTPSが必須)
app.config["JWT_COOKIE_CSRF_PROTECT"] = True # CSRF保護を有効化
app.config["JWT_ACCESS_COOKIE_NAME"] = "access_token_cookie"
app.config["JWT_REFRESH_COOKIE_NAME"] = "refresh_token_cookie"
app.config["JWT_ACCESS_CSRF_HEADER_NAME"] = "X-CSRF-TOKEN" # CSRFトークンを受け取るヘッダー名

jwt = JWTManager(app)

# ... (ダミーユーザー情報など) ...

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username', None)
    password = request.json.get('password', None)
    # ... (ユーザー認証) ...
    if not valid_user:
       return jsonify({"msg": "Bad username or password"}), 401

    access_token = create_access_token(identity=username)
    refresh_token = create_refresh_token(identity=username)

    # レスポンスを作成し、Cookieを設定
    response = jsonify({"msg": "Login successful"})
    set_access_cookies(response, access_token)
    set_refresh_cookies(response, refresh_token)
    return response

@app.route('/logout', methods=['POST'])
def logout():
    # Cookieを削除するためのレスポンスを作成
    response = jsonify({"msg": "Logout successful"})
    unset_jwt_cookies(response)
    return response

# Cookieベースの認証 + CSRF保護が必要なエンドポイント
@app.route('/protected_cookie', methods=['GET'])
@jwt_required() # locations=['cookies'] はJWT_TOKEN_LOCATIONで指定済みなら不要
def protected_cookie():
    # CSRF保護が有効な場合、適切なCSRFトークンがヘッダーに含まれている必要がある
    current_user_identity = get_jwt_identity()
    return jsonify(hello=current_user_identity)

if __name__ == '__main__':
    app.run(debug=True)

エラーハンドリングのカスタマイズ

Flask-JWT-Extended は、トークン関連のエラー(期限切れ、無効、見つからない、不正な形式など)が発生した場合に、デフォルトのエラーレスポンスを返します。これらのレスポンスをカスタマイズしたい場合は、専用のデコレータを使用してエラーハンドラ関数を登録できます。

  • @jwt.expired_token_loader: トークンの有効期限が切れている場合
  • @jwt.invalid_token_loader: トークンが無効な場合(署名が違うなど)
  • @jwt.unauthorized_loader: トークンが見つからない、またはヘッダーが不正な場合
  • @jwt.needs_fresh_token_loader: fresh=True が要求されているが、トークンがfreshでない場合
  • @jwt.revoked_token_loader: トークンがブロックリストに含まれている場合
  • @jwt.user_lookup_error_loader: ユーザー検索コールバックでエラーが発生した場合
  • @jwt.claims_verification_failed_loader: クレーム検証に失敗した場合

これらのデコレータで修飾された関数は、対応するエラーが発生した際に呼び出されます。関数は通常、エラーに関する情報(例: 期限切れトークンのペイロード)を引数として受け取り、カスタマイズされたJSONレスポンスとステータスコードを返すように実装します。

from flask import Flask, jsonify
from flask_jwt_extended import JWTManager
import os

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY", "super-secret-key-for-dev")
jwt = JWTManager(app)

# ... (他のエンドポイントや設定) ...

@jwt.expired_token_loader
def expired_token_callback(jwt_header, jwt_payload):
    # jwt_payload には期限切れトークンのペイロードが含まれる
    identity = jwt_payload.get('sub') # 'sub' クレームが identity
    return jsonify(
        code="TOKEN_EXPIRED",
        err_msg="The token has expired. Please refresh.",
        # identity=identity # 必要であれば identity も返す
    ), 401 # 401 Unauthorized を返すのが一般的

@jwt.invalid_token_loader
def invalid_token_callback(error_string):
    # error_string にはエラーの理由が含まれる
    return jsonify(
        code="INVALID_TOKEN",
        err_msg=f"Invalid token: {error_string}"
    ), 422 # 422 Unprocessable Entity または 401

@jwt.unauthorized_loader
def missing_token_callback(error_string):
    return jsonify(
        code="AUTHORIZATION_REQUIRED",
        err_msg=f"Authorization required: {error_string}"
    ), 401

@jwt.revoked_token_loader
def revoked_token_callback(jwt_header, jwt_payload):
    return jsonify(
        code="TOKEN_REVOKED",
        err_msg="Token has been revoked (logged out). Please log in again."
    ), 401

if __name__ == '__main__':
    app.run(debug=True)

これにより、フロントエンドアプリケーションがエラーの種類を判別しやすくなり、より適切なユーザーフィードバック(例: 「トークンの有効期限が切れました。更新しますか?」や「不正なトークンです。再ログインしてください。」)を表示できるようになります。

Flask-JWT-Extended は多くの設定オプションを提供しており、アプリケーションの要件に合わせて挙動を細かく調整できます。以下は主要な設定オプションの一部です。

設定キー 説明 デフォルト値
JWT_SECRET_KEY JWTの署名に使用する秘密鍵。必須設定であり、安全な値を設定する必要があります。 None
JWT_ALGORITHM JWTの署名アルゴリズム。 'HS256'
JWT_IDENTITY_CLAIM JWTペイロード内でIdentityを格納するクレーム名。 'sub' (Subject)
JWT_ACCESS_TOKEN_EXPIRES アクセストークンの有効期間。datetime.timedelta オブジェクト、秒数を表す整数、または False(無期限)を指定。 timedelta(minutes=15)
JWT_REFRESH_TOKEN_EXPIRES リフレッシュトークンの有効期間。同上。 timedelta(days=30)
JWT_TOKEN_LOCATION JWTを検索する場所のリスト。'headers', 'cookies', 'query_string', 'json' を指定可能。 ['headers']
JWT_HEADER_NAME ヘッダーからJWTを検索する場合のヘッダー名。 'Authorization'
JWT_HEADER_TYPE ヘッダーからJWTを検索する場合のタイプ(例: ‘Bearer’)。値が指定されている場合、ヘッダー値はこのタイプで始まる必要があります。 'Bearer'
JWT_JSON_KEY JSONボディからJWTを検索する場合のキー名。 'access_token'
JWT_REFRESH_JSON_KEY JSONボディからリフレッシュトークンを検索する場合のキー名。 'refresh_token'
JWT_QUERY_STRING_NAME クエリパラメータからJWTを検索する場合のパラメータ名。 'jwt'
JWT_ACCESS_COOKIE_NAME アクセストークンを格納するCookie名。 'access_token_cookie'
JWT_REFRESH_COOKIE_NAME リフレッシュトークンを格納するCookie名。 'refresh_token_cookie'
JWT_COOKIE_SECURE CookieのSecure属性を設定するかどうか。本番環境(HTTPS)では True にすべきです。 False
JWT_COOKIE_HTTPONLY CookieのHttpOnly属性を設定するかどうか。通常は True(JavaScriptからアクセス不可)が推奨されます。 True
JWT_COOKIE_SAMESITE CookieのSameSite属性。CSRF対策として 'Lax' または 'Strict' が推奨されます。 None
JWT_COOKIE_CSRF_PROTECT Cookieベースの認証でCSRF保護を有効にするか。 True (v4+), False (v3)
JWT_ACCESS_CSRF_HEADER_NAME CSRF保護が有効な場合に、アクセストークン用のCSRFトークンを受け取るヘッダー名。 'X-CSRF-TOKEN'
JWT_BLOCKLIST_ENABLED ブロックリスト(旧ブラックリスト)機能を有効にするか。 False
JWT_BLOCKLIST_TOKEN_CHECKS ブロックリストチェックを行うトークンの種類 ('access', 'refresh')。 ['access', 'refresh']

上記以外にも多くの設定オプションがあります。詳細については、Flask-JWT-Extended の公式ドキュメントを参照してください。設定を適切に行うことで、よりセキュアで柔軟な認証システムを構築できます。

まとめ ✨

Flask-JWT-Extended は、FlaskアプリケーションにJWTベースの認証機能を実装するための非常に強力で柔軟なライブラリです。基本的なトークンの生成と検証から、リフレッシュトークン、カスタムクレーム、トークンの無効化(ブロックリスト)、Cookieベース認証、CSRF保護、柔軟なエラーハンドリングまで、幅広い機能を提供しています。

この記事では、その主な機能と使い方を解説しました。

  • ✅ インストールと基本的なセットアップ
  • ✅ アクセストークンとリフレッシュトークンの生成と利用(create_access_token, create_refresh_token, @jwt_required, get_jwt_identity
  • ✅ リフレッシュトークンによるアクセストークンの更新(@jwt_required(refresh=True)
  • ✅ カスタムクレームの追加(@jwt.additional_claims_loader, get_jwt
  • ✅ トークンの無効化(ブロックリスト、JWT_BLOCKLIST_ENABLED, @jwt.token_in_blocklist_loader
  • ✅ トークンロケーションのカスタマイズ(JWT_TOKEN_LOCATION)とCookie利用時の注意点(CSRF)
  • ✅ エラーハンドリングのカスタマイズ
  • ✅ 主要な設定オプション

これらの機能を活用することで、セキュアで使いやすい認証システムを効率的に構築できます。Flask-JWT-Extended は活発に開発されており、最新の機能やベストプラクティスについては公式ドキュメントを確認することが常に推奨されます。

ぜひ、あなたの次のFlaskプロジェクトで Flask-JWT-Extended を活用してみてください!🚀

コメント

タイトルとURLをコピーしました