INDEX
認証の仕組みと実装

Webサービスやモバイルアプリの裏側で動くAPI(Application Programming Interface)を開発する際、セキュリティの担保は最優先で取り組むべき課題です。
特に、誰がシステムにアクセスしているのかを確認する「認証」と、その人に実行権限があるかを制御する「認可」を適切に設計・実装しないと、重大なデータ漏洩や不正操作を招く脆弱性につながります。本記事では、API認証の基本概念から、現代のシステム開発で標準となっているJWT(JSON Web Token)を用いた実装方法までを解説します。
1. 認証の基本概念と3ステップ
API認証とは、リクエストの送信者が「本人であるか」「正当なシステム利用者であるか」を特定・検証するプロセスです。一般的なWeb APIでは、以下の3つのステップに沿って処理が進行します。
- 認証情報の送信: クライアント(ブラウザやスマホアプリなど)は、初回ログイン時にユーザー名とパスワードを暗号化(HTTPS)してサーバーへ送信します。
- 認証情報の検証: サーバー側は、受け取ったパスワードを安全な方法でハッシュ化し、データベース内に保管されているハッシュ値と照合します。
- 認証状態の永続化(トークンの発行): 照合が成功すると、サーバーはアクセスの許可証として「一意のトークン」を発行してクライアントに返します。次回以降、クライアントはこのトークンをリクエストのヘッダーに添付することで、毎回パスワードを送信することなく保護されたAPIにアクセスできるようになります。
2. 主なAPI認証方式の種類と特徴
システムの規模やセキュリティ要件に応じて、いくつかの認証方式が使い分けられます。
- APIキー認証: 固有の長い文字列(キー)をリクエストに含めるだけのシンプルな方式です。手軽ですが、キーの漏洩リスクが高く、ユーザーごとの細かい権限管理が難しいため、公開されている天気予報APIなどの重要度の低いデータ取得によく使われます。
- OAuth 2.0(オーオース): 「Googleでログイン」や「LINE連携」のように、サードパーティ製アプリに対してユーザーのパスワードを教えることなく、特定のデータへのアクセス権限を安全に委譲するための業界標準フレームワークです。
- JWT(JSON Web Token): トークン自体の中に「ユーザーID」や「有効期限」などの情報が電子署名付きで埋め込まれている、データ密結合型のトークン認証方式です。サーバー側でセッション状態(ログイン中かどうかのデータ)をデータベースやメモリ(Redisなど)に保持して毎回照合する必要がない(ステートレスである)ため、アクセスが大量に集中するモダンなマイクロサービスやクラウド環境のAPI開発で最も好まれます。
3. Python(Flask)による基本認証の実装例
Pythonの軽量Webフレームワークである「Flask」を使い、ユーザー名とパスワードを用いた認証 APIの基本的な実装構造を示します。
from flask import Flask, request, jsonify
import hashlib
app = Flask(__name__)
# サンプルのユーザーデータベース
# セキュリティ上の絶対ルールとして、パスワードは生文(プレーンテキスト)のまま保存してはいけません。
# ここではあらかじめSHA-256でハッシュ化した値を登録しています。
users = {
"user1": hashlib.sha256("password1".encode()).hexdigest(),
"user2": hashlib.sha256("password2".encode()).hexdigest(),
}
@app.route('/login', methods=['POST'])
def login():
# リクエストからBasic認証などの認証情報を取得
auth = request.authorization
if not auth or not auth.username or not auth.password:
return jsonify({"message": "認証情報が不足しています"}), 401
username = auth.username
# 入力されたパスワードをハッシュ化して比較の準備をする
input_password_hash = hashlib.sha256(auth.password.encode()).hexdigest()
# ユーザー名が登録されており、かつハッシュ化したパスワードが一致するか検証
if username in users and users[username] == input_password_hash:
return jsonify({"message": f"ようこそ、{username}さん"}), 200
else:
# 認証失敗時は、一律で401 Unauthorizedステータスを返す(ハッカーへのヒントを減らすため)
return jsonify({"message": "ユーザー名またはパスワードが正しくありません"}), 401
if __name__ == '__main__':
app.run(debug=True)
4. JWT(JSON Web Token)認証の本格的実装
次に、実務で多用されるJWTを利用した認証システムの実装です。このコードを実行するには、外部ライブラリの PyJWT が必要となるため、あらかじめ pip install PyJWT でインストールを行ってください。
import jwt
import datetime
from flask import Flask, request, jsonify
app = Flask(__name__)
# JWTの暗号化署名に使う秘密鍵(本番環境では必ず環境変数から取得し、厳重に秘匿してください)
app.config['SECRET_KEY'] = 'your_super_secret_key_string'
def generate_token(username):
"""ユーザー名と有効期限を含んだ暗号化トークンを発行する"""
payload = {
'username': username,
# トークンの有効期限を「現在時刻から30分間」に設定
'exp': datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=30)
}
# 秘密鍵とHS256アルゴリズムを使用してトークンを署名・生成
token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
return token
@app.route('/login', methods=['POST'])
def login():
auth = request.authorization
# 簡易的なユーザー検証
if auth and auth.username == 'user1' and auth.password == 'password1':
# 認証成功時にJWTを発行してクライアントに返す
token = generate_token(auth.username)
return jsonify({"token": token}), 200
return jsonify({"message": "認証に失敗しました"}), 401
@app.route('/protected', methods=['GET'])
def protected_api():
"""有効なトークンを持っていないとアクセスできない保護されたAPI"""
# リクエストヘッダーの 'Authorization' からトークンを回収
token = request.headers.get('Authorization')
if not token:
return jsonify({"message": "トークン(許可証)が添付されていません"}), 401
try:
# 送られてきたトークンを秘密鍵で復号・検証
# 改ざんされていないか、有効期限(exp)が切れていないかを自動チェックします
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
return jsonify({"message": f"認証成功。ようこそ{data['username']}さん。保護されたデータです。"}), 200
except jwt.ExpiredSignatureError:
return jsonify({"message": "トークンの有効期限が切れています。再度ログインしてください。"}), 401
except jwt.InvalidTokenError:
return jsonify({"message": "トークンが不正、または改ざんされています。"}), 401
if __name__ == '__main__':
app.run(debug=True)
主要な認証方式の比較・使い分けまとめ
API開発時にどの設計を選択するべきかの判断基準です。
| 認証方式 | サーバー側での状態管理(セッション) | 主なメリット | 主なデメリット・注意点 |
|---|---|---|---|
| APIキー認証 | 不要(データベースでキーを突合) | 実装が非常に簡単。リクエストごとのオーバーヘッドが最小限。 | キーが漏洩した際の被害が大きい。有効期限の設定や個別無効化が難しい。 |
| 従来のセッション認証 | 必要(メモリやDBにログイン状態を記録) | サーバー側からいつでも強制的にログアウト(セッション破棄)を操作できる。 | サーバーが複数台に増えた(スケールアウト)際、セッション情報の同期が必要になり管理が複雑化する。 |
| JWT認証 | 完全に不要(ステートレス) | トークン自体がデータを持つため、サーバー台数が増えても検証が容易。水平分散に強い。 | 一度発行したトークンは、有効期限が切れるまでサーバー側から強制失効させることが困難。 |
まとめ
- パスワードは絶対にそのまま保存しない: 初期のハッシュ化ロジックの選定を含め、ユーザーのパスワードは
hashlibなどを経由させ、復元不可能なハッシュ値としてデータベースに保持する設計を徹底してください。 - モダンなAPIにはJWTが最適: サーバー側のリソース(セッションメモリ)を圧迫せず、改ざん検知の仕組みがトークン自体に内蔵されているJWTは、現代のWebアプリケーション構築におけるスタンダードなアプローチです。
- トークンの寿命と漏洩対策をセットにする: JWTはステートレスで便利な反面、盗まれた際の強制無効化が難しいため、有効期限(
exp)を短く設定する、あるいはリフレッシュトークン(再発行用トークン)の仕組みと併用して運用する工夫が必要です。