Contents
Flask API 認証 JWT 実装手順:実践的なセキュリティ設計ガイド
Flask API における JWT(JSON Web Token)認証の導入は、API の信頼性と安全性を確保する上で不可欠な技術です。本記事では、PyJWT ライブラリを活用した具体的な実装手順をステップバイステップで解説します。初心者でも理解しやすいコードサンプル付きで、セキュリティ設計のコツも紹介します。
FlaskとPyJWTの導入手順
Flask アプリケーションに JWT 認証を実装するにはまず、必要なライブラリをインストールします。Python 3.7以降、Flask 2.x との互換性を確保した PyJWT の最新バージョンを推奨します。
必要なライブラリのインストール
以下のコマンドで Flask と PyJWT をインストールしてください。環境ごとにバージョン管理が重要になるため、requirements.txtに記録することをお勧めします。
|
1 2 |
pip install Flask==2.0.1 pyjwt==2.4.0 python-dotenv |
注意:
pyjwt[crypto]はRSA系のアルゴリズム(RS256など)で使用されるため、HS256では不要です。アルゴリズム選択に応じてインストールしてください。
プロジェクト構成の確認
以下のようなシンプルなプロジェクト構成を前提とします。
|
1 2 3 4 5 |
flask_jwt_project/ ├── app.py # Flaskアプリケーション本体 └── .env # 環境変数設定ファイル └── requirements.txt |
app.py の初期コード例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from flask import Flask, jsonify import os app = Flask(__name__) # 環境変数からSECRET_KEYを読み込む(.envファイルに設定すること) app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', 'default-secret-key') @app.route('/api') def api(): return jsonify(message="Hello, World!") if __name__ == '__main__': app.run(debug=True) |
この状態ではセキュリティ対策がありません。次は JWT 認証の実装に移ります。
トークン発行処理の実装
認証エンドポイントを設置し、ユーザーの認証情報をもとに JWT を発行します。/login エンドポイントで POST リクエストを受け取ることで、認証フローを構築できます。
認証エンドポイントの作成
以下は、POST リクエストでユーザー名とパスワードを受け取り、正当な場合に JWT を発行する例です。実際にはデータベースでの照合が必要ですが、このサンプルでは簡略化しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from flask import Flask, jsonify, request import jwt from datetime import datetime, timedelta app = Flask(__name__) app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', 'default-secret-key') @app.route('/login', methods=['POST']) def login(): username = request.json.get('username') password = request.json.get('password') if username == 'test' and password == 'test': payload = { 'exp': datetime.utcnow() + timedelta(hours=1), 'iat': datetime.utcnow(), 'sub': username } token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256') return jsonify(token=token), 200 return jsonify(message="Invalid credentials"), 401 |
ポイント:
exp(有効期限)とiat(発行時刻)は必須の claim です。トークンの信頼性を担保するために、適切な値を設定しましょう。
リクエスト検証ロジックの構築
JWT の検証ロジックを実装することで、APIリソースへのアクセスを制限できます。ミドルウェアとしてトークンの解読と署名検証を行い、不正なリクエストをブロックします。
トークン検証ミドルウェアの実装
以下は、@jwt_required() デコレータのように動作するミドルウェアの例です。各 API 関数でこのデコレータを適用することで自動的に検証が行われます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from functools import wraps import jwt def jwt_required(func): @wraps(func) def wrapper(*args, **kwargs): # AuthorizationヘッダのBearer形式チェック auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify(message="Invalid token format"), 401 token = auth_header.split(' ')[1] try: payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) except jwt.ExpiredSignatureError: return jsonify(message="Token has expired"), 401 except jwt.InvalidTokenError: return jsonify(message="Invalid token"), 401 # 認証成功時の処理(例: payloadからユーザー情報を取得) return func(*args, **kwargs) return wrapper @app.route('/api/secure', methods=['GET']) @jwt_required def secure_api(): return jsonify(message="Secure API access granted!") |
権限レベルチェックの方法
複数のユーザータイプ(一般ユーザー、管理者など)を管理する場合、payload に role クレームなどを含めます。検証時にその値を確認してアクセスを制御できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def check_admin_role(func): @wraps(func) def wrapper(*args, **kwargs): payload = jwt.decode(...) if payload.get('role') != 'admin': return jsonify(message="Permission denied"), 403 return func(*args, **kwargs) return wrapper @app.route('/api/admin', methods=['GET']) @jwt_required @check_admin_role def admin_api(): return jsonify(message="Admin API access granted!") |
セキュリティベストプラクティス
JWT 認証の実装には、以下のようなセキュリティ対策が必須です。特に秘密鍵管理とトークン有効期限に関する知識は重要です。
秘密鍵管理の注意点
- 環境変数での保存:
app.config['SECRET_KEY']ではなく、.envファイルに設定し、.gitignoreで排除してください。 - 定期的な更新: 長期的に使用する場合は、秘密鍵を一定期間ごとに変更します。
トークン有効期限の考え方
| タイプ | 推奨値 | メリット |
|---|---|---|
| アクセス・トークン | 1時間以内 | 無効化が速やかに可能 |
| リフレッシュ・トークン | 7日以内 | ユーザーの不便を最小限に |
注意: 長時間有効なトークンは、漏洩時のリスクが高まります。リフレッシュ・トークンを併用する方法もあります。
HTTPSの必須性
API通信においてHTTPSの使用は必須です。HTTPでは通信内容が盗聴される可能性があり、セキュリティに重大な影響を与えます。本格的な運用では必ずHTTPSを採用してください。
トークン失効処理とリフレッシュトークン
トークン失効処理の実装
JWTは署名検証によって有効性が判断されるため、即時失効が難しい特性があります。これを補うために「ブラックリスト(失効済みトークンリスト)」を導入します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# ブラックリストを保存するDBやInMemoryオブジェクトを作成 blacklist = set() def is_token_blacklisted(token): return token in blacklist @app.route('/api/revoke', methods=['POST']) @jwt_required def revoke_token(): # ユーザーが自身のトークンを失効させる処理 token = request.headers.get('Authorization').split(' ')[1] blacklist.add(token) return jsonify(message="Token revoked"), 200 |
リフレッシュトークンの実装
リフレッシュトークンは長期間有効なトークンで、アクセストークンを再発行する際の認証手段として使用されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@app.route('/refresh', methods=['POST']) @jwt_required def refresh(): # リフレッシュトークンにアクセス権限があるかチェック if payload.get('is_refresh_token'): new_access_token = jwt.encode( {'exp': datetime.utcnow() + timedelta(minutes=30), 'sub': payload['sub']}, app.config['SECRET_KEY'], algorithm='HS256' ) return jsonify(token=new_access_token), 200 return jsonify(message="Invalid refresh token"), 401 |
エラーハンドリングの具体例
JWT 認証中に発生するエラー(トークン有効期限切れや不正な署名など)を適切に処理することで、ユーザー体験とセキュリティの両立が可能です。
認証失敗時のレスポンス設計
以下は PyJWT から発生する例外をキャッチし、汎用的なエラーレスポンスを返す例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from flask import Flask, jsonify, request import jwt app = Flask(__name__) app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', 'default-secret-key') @app.route('/api') def secure_api(): token = request.headers.get('Authorization') try: payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) except jwt.ExpiredSignatureError as e: return jsonify(error="Token has expired", detail=str(e)), 401 except jwt.InvalidTokenError as e: return jsonify(error="Invalid token format", detail=str(e)), 401 return jsonify(message=f"Access granted for {payload['sub']}") |
例外処理の共通化
複数のエンドポイントで同じような処理を避け、ミドルウェアとして共通化することで保守性が向上します。以下はその例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from functools import wraps def handle_jwt_errors(func): @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except jwt.ExpiredSignatureError as e: return jsonify(error="Token expired", detail=str(e)), 401 except jwt.InvalidTokenError as e: return jsonify(error="Invalid token", detail=str(e)), 401 return wrapper @app.route('/api') @handle_jwt_errors def secure_api(): # 認証成功時の処理 |
まとめ
- FlaskとPyJWTの導入手順:
pip install Flask pyjwt python-dotenvでインストールし、プロジェクト構成を整える。 - トークン発行処理:
/loginエンドポイントでユーザー認証を行い、ペイロードに有効期限・発行時刻などのclaimを設定。 - リクエスト検証ロジック:トークンの署名検証と、アクセス制御に役立つ権限チェックをミドルウェアとして実装。Authorizationヘッダには
Bearer形式で送信。 - セキュリティベストプラクティス:
- 秘密鍵は環境変数で管理し、有効期限は短めに設定
- リフレッシュトークンを併用して安全を確保
- HTTPSの必須性を理解し導入
- エラーハンドリング:PyJWTの例外を適切に処理し、401や403ステータスコードで明確なレスポンスを返す。
これらの手順と実装例を参考に、Flask API における JWT 認証を安全かつ効率的に導入してください。