---
title: "FastAPIのJWT認証｜PyJWTで発行・検証・リフレッシュトークンをコードで実装"
url: "https://www.issoh.co.jp/tech/details/19747/"
published: 2026-11-14
updated: 2026-11-14
categories: ["開発"]
publisher: "株式会社一創"
---

# FastAPIのJWT認証｜PyJWTで発行・検証・リフレッシュトークンをコードで実装

FastAPIでJWT認証を組むときに手が止まるのは、ライブラリの選び方と、トークンの検証をどこまで厳しく書くかの2点です。この記事では、FastAPI 0.162.7とPyJWT 2.15.1を使い、ログインでアクセストークンとリフレッシュトークンを発行し、`OAuth2PasswordBearer`と`Depends`で保護したルートを作るまでをコードで示します。`exp`・`aud`・`iss`の検証、PyJWTが送出する例外の扱い、リフレッシュトークンのローテーションと再利用の検知も、手元で実行した結果つきで扱います。最後に示すのは、JWTを自前で発行せずIdPに任せるべき条件です。

## まとめ：FastAPIでJWT認証を組むときのライブラリ選定と実装の結論

ライブラリはPyJWTとpwdlibを選びます。FastAPIの公式チュートリアルは0.111.1でpython-joseからPyJWTへ、0.118.0でpasslibからpwdlib（Argon2）へ書き換えられました。python-joseの最終リリースは2025年5月の3.5.0、passlibは2020年10月の1.7.4で止まっています。古い記事のコードを写すと、この2つを新しく入れることになります。

検証の結論は3つです。`jwt.decode`には`algorithms`を必ず渡し、`audience`と`issuer`も照合します。PyJWTの例外は`jwt.InvalidTokenError`でまとめて受け、応答をすべて401に寄せる方針です。アクセス用とリフレッシュ用は`type`クレームで区別し、取り違えを拒否します。

リフレッシュトークンは`jti`をサーバー側に保存し、使うたびに新しい1本と交換します。使用済みの1本が再び来たら漏えいとみなし、その利用者のリフレッシュトークンを全て失効させる処理です。外部のIdP（Auth0、Amazon Cognito、Microsoft Entra IDなど）でログインさせるなら、発行は書かずにJWKSでの検証だけを書きます。

## FastAPI 0.162系とPyJWT 2.15系でJWT認証の検証環境を作る手順

最初に、版と依存ライブラリを固めます。JWTまわりは古い記事が多く、版の違いがそのまま挙動の違いになるためです。

### Python 3.11以上でJWT認証用の依存ライブラリをそろえる手順

2026年11月14日時点のPyPIで、[FastAPIの最新は0.162.7](https://pypi.org/project/fastapi/)（2026年11月13公開）で、動作要件はPython 3.11以上です。[PyJWTは2.15.1](https://pypi.org/project/PyJWT/)（2026年9月28日公開）、[pwdlibは0.3.1](https://pypi.org/project/pwdlib/)が最新でした。この記事のコードは`datetime.UTC`を使うので、3.11以上が前提です。FastAPI自体の起動手順は[FastAPIの最短の起動手順とasync/defの使い分け](https://www.issoh.co.jp/tech/details/3859/)で扱っています。

```
# 仮想環境を作り、FastAPI・PyJWT・pwdlib（Argon2）・pytestを入れる
python3 -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]==0.162.7" "pyjwt==2.15.1" "pwdlib[argon2]==0.3.1" pytest

# 署名用の鍵を作り、環境変数で渡す（コードやGitに書かない）
export JWT_SECRET_KEY="$(openssl rand -hex 32)"

fastapi dev main.py
```

`fastapi[standard]`には、ログインのフォームを読むのに要るpython-multipartが含まれます。RS256などの公開鍵署名を使う場合は、PyJWTを`pyjwt[crypto]`で入れてcryptographyを足します。

### python-joseとpasslibからPyJWTとpwdlibへ移った公式の経緯

[FastAPIのリリースノート](https://fastapi.tiangolo.com/release-notes/)をたどると、JWTの説明は一度入れ替わっています。0.59.0（2020年7月）でPyJWTからpython-joseに替わり、0.111.1（2024年7月）でPyJWTへ戻りました。パスワードのハッシュは0.118.0（2025年9月）でpasslibからpwdlibとArgon2に替わっています。

日本語の解説記事には、`from jose import jwt`と`CryptContext(schemes=["bcrypt"])`の組み合わせがまだ多く残っています。新しく書くコードでそれを選ぶ理由はありません。既存システムでpython-joseを使っている場合も、`jwt.encode`と`jwt.decode`の引数はほぼ同じ形なので、置き換えは例外クラスの名前を`JWTError`から`InvalidTokenError`に直す作業が中心になります。

## tokens.pyでアクセストークンを発行しPyJWTで検証するコード

トークンの発行と検証は、FastAPIに依存しない`tokens.py`に分けます。HTTPを通さずにテストでき、後でIdPの検証に差し替えるときも触るのはこのファイルだけで済みます。

```
# tokens.py
import os
import uuid
from datetime import UTC, datetime, timedelta

import jwt

# openssl rand -hex 32 で作った値を環境変数で渡す（コードに書かない）
SECRET_KEY = os.environ["JWT_SECRET_KEY"]
ALGORITHM = "HS256"
ISSUER = "https://api.example.jp"
AUDIENCE = "example-api"
ACCESS_TTL = timedelta(minutes=15)
REFRESH_TTL = timedelta(days=14)

# 有効なリフレッシュトークンの jti と sub の対応。本番では Redis やDBのテーブルに置く
REFRESH_STORE: dict[str, str] = {}


def create_token(sub: str, token_type: str, ttl: timedelta) -> tuple[str, str]:
    now = datetime.now(UTC)
    jti = uuid.uuid4().hex
    payload = {
        "sub": sub,
        "type": token_type,
        "jti": jti,
        "iat": now,
        "exp": now + ttl,
        "iss": ISSUER,
        "aud": AUDIENCE,
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM), jti


def decode_token(token: str, expected_type: str) -> dict:
    payload = jwt.decode(
        token,
        SECRET_KEY,
        algorithms=[ALGORITHM],
        audience=AUDIENCE,
        issuer=ISSUER,
        leeway=30,
        options={"require": ["exp", "iat", "sub", "jti", "type"]},
    )
    if payload["type"] != expected_type:
        raise jwt.InvalidTokenError("unexpected token type")
    return payload


def issue_pair(sub: str) -> dict:
    access, _ = create_token(sub, "access", ACCESS_TTL)
    refresh, jti = create_token(sub, "refresh", REFRESH_TTL)
    REFRESH_STORE[jti] = sub
    return {"access_token": access, "refresh_token": refresh, "token_type": "bearer"}


def rotate(refresh_token: str) -> dict:
    payload = decode_token(refresh_token, "refresh")
    if REFRESH_STORE.pop(payload["jti"], None) is None:
        # 使用済みのリフレッシュトークンが再提示された。漏えいを疑い、本人の分を全て失効させる
        for jti in [k for k, v in REFRESH_STORE.items() if v == payload["sub"]]:
            del REFRESH_STORE[jti]
        raise jwt.InvalidTokenError("refresh token reused")
    return issue_pair(payload["sub"])
```

### sub・exp・iss・aud・jtiを入れたペイロードと有効期限15分の設計

クレームの名前と意味は[RFC 7519](https://www.rfc-editor.org/rfc/rfc7519)で決まっています。`sub`に入れる値は、利用者を一意に指す文字列です。[公式チュートリアル](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/)も、`sub`はアプリ全体で一意な文字列にし、ID空間が重なるなら`username:johndoe`のように接頭辞を付けるよう書いています。管理者と一般利用者を別テーブルで持つ場合は`admin:1`と`user:1`のように分け、同じ番号を取り違えないようにします。

PyJWTは`datetime`を渡せばUNIX秒に変換して書き込みます。アクセストークンを15分にしたのは、署名の検証だけで通すJWTは期限が来るまで止められないからです。止められない時間を短くし、その代わりにリフレッシュトークンで更新させます。寿命の決め方の考え方は[リフレッシュトークンの寿命設計とローテーションの解説](https://www.issoh.co.jp/tech/details/16409/)にまとめています。

### decodeのalgorithms指定と発行者・受信者の照合が必要な理由

`algorithms`は省略できません。PyJWT 2.15.1で省くと`DecodeError`になり、「It is required that you pass in a value for the “algorithms” argument」と返ります。ヘッダーの`alg`をそのまま信じると、`alg: none`の署名なしトークンを通す事故につながるためで、[RFC 8725](https://www.rfc-editor.org/rfc/rfc8725)の3.1節も、使うアルゴリズムを受け手が決めて照合するよう求めています。手元で`alg: none`のトークンを作って渡すと、`InvalidAlgorithmError`で拒否されました。

`audience`と`issuer`の照合（RFC 8725の3.8節と3.9節）は、同じ鍵を持つ別のサービスが出したトークンを受け付けないための設定です。ここで注意したいのは、次の1点です。`aud`入りのトークンを`audience`未指定で検証すると、PyJWTは`InvalidAudienceError`で拒否します。発行側だけ`aud`を足すと、検証側の全エンドポイントが401を返し始める原因になるため、発行側だけの変更は禁物です。発行と検証の設定は同時に変えます。

### PyJWT 2.15.1で実測した例外の種類と401に寄せるときの書き分け

[PyJWTのUsage Examples](https://pyjwt.readthedocs.io/en/stable/usage.html)に載っている例外を、2.15.1で実際に起こして確かめました。下の表の例外は、どれも`jwt.InvalidTokenError`の子クラスです。

| 起こした状況         | 送出された例外                   |
| -------------- | ------------------------- |
| 期限切れ（exp超過）    | ExpiredSignatureError     |
| 鍵が違う           | InvalidSignatureError     |
| audの不一致・未指定    | InvalidAudienceError      |
| issの不一致        | InvalidIssuerError        |
| requireのクレーム欠落 | MissingRequiredClaimError |
| subが整数         | InvalidSubjectError       |
| alg=none       | InvalidAlgorithmError     |

応答はすべて401にそろえ、どの検査で落ちたかは本文に書きません。期限切れだけをクライアントに知らせたい場合は、`ExpiredSignatureError`を先に`except`して`WWW-Authenticate`に`error="invalid_token"`を付けます。[RFC 6750](https://www.rfc-editor.org/rfc/rfc6750)の3.1節が定める形で、フロントエンドはこれを見て更新に進める仕組みです。`sub`を整数で入れると検証側で落ちる点は見落としやすく、DBの主キーをそのまま入れるときは`str()`で文字列にします。

## OAuth2PasswordBearerとDependsでログインと保護ルートを実装する

次に、`tokens.py`をFastAPIにつなぎます。ログイン、トークンの更新、保護したルートの3本です。

```
# main.py
from typing import Annotated

import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pwdlib import PasswordHash
from pydantic import BaseModel

import tokens

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
password_hash = PasswordHash.recommended()
DUMMY_HASH = password_hash.hash("dummy-password")

# 検証用の利用者表。本番ではDBから引く
USERS = {"alice": {"id": "user:1", "hashed_password": password_hash.hash("wonderland")}}


def credentials_error() -> HTTPException:
    return HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )


class RefreshRequest(BaseModel):
    refresh_token: str


@app.post("/token")
def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
    user = USERS.get(form.username)
    if user is None:
        password_hash.verify(form.password, DUMMY_HASH)  # 応答時間をそろえる
        raise credentials_error()
    if not password_hash.verify(form.password, user["hashed_password"]):
        raise credentials_error()
    return tokens.issue_pair(user["id"])


@app.post("/token/refresh")
def refresh(body: RefreshRequest):
    try:
        return tokens.rotate(body.refresh_token)
    except jwt.InvalidTokenError:
        raise credentials_error() from None


def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> str:
    try:
        payload = tokens.decode_token(token, "access")
    except jwt.InvalidTokenError:
        raise credentials_error() from None
    return payload["sub"]


CurrentUser = Annotated[str, Depends(get_current_user)]


@app.get("/users/me")
def read_me(user_id: CurrentUser):
    return {"user_id": user_id}
```

### /tokenでログインフォームを受けてトークンを発行する処理

`OAuth2PasswordRequestForm`は、OAuth2のパスワードフローが決める`username`と`password`をフォーム形式で受け取ります。JSONではない点に注意してください。フロントエンドから`fetch`でJSONを送ると422になります。

存在しない利用者名でも`DUMMY_HASH`に対してパスワードの照合を1回走らせているのは、公式チュートリアルにある対策です。照合を省くと、存在しない名前だけ応答が速く返り、応答時間から登録済みの利用者名を割り出されます。Argon2の照合は意図的に重く作られているので、この差は外から測れる大きさになります。

### get\_current\_userで署名検証してユーザーを引く依存関数の書き方

`oauth2_scheme`は`Authorization: Bearer <トークン>`ヘッダーから文字列を取り出すだけで、中身は見ません。FastAPI 0.162.7のソース（`fastapi/security/oauth2.py`）では、ヘッダーが無いかBearer以外なら、detailが「Not authenticated」の401を`WWW-Authenticate: Bearer`つきで返します。0.122.0より前のHTTPBearerなどは、この場面で403を返していました。古い版でテストを書いていた場合は期待値を見直します。

署名と期限の検証は`get_current_user`の中で`tokens.decode_token`に任せます。`CurrentUser`を引数に書いたルートだけが守られ、`Depends`の連鎖として`oauth2_scheme`から順に解決される仕組みです。連鎖の仕組みや`dependency_overrides`での差し替えは[FastAPIのDependsの使い方](https://www.issoh.co.jp/tech/details/19720/)で、ルーター単位で一括して掛ける方法は[APIRouterでの依存の掛け方](https://www.issoh.co.jp/tech/details/19741/)で扱っています。ヘッダーの書式そのものは[Bearer Tokenの書き方とJWTとの違い](https://www.issoh.co.jp/tech/details/4150/)が詳しく説明しています。

### Swagger UIのAuthorizeボタンとcurlで401と200を確かめる手順

`OAuth2PasswordBearer(tokenUrl="token")`を使うと、`/docs`の右上にAuthorizeボタンが出ます。利用者名とパスワードを入れると`/token`が呼ばれ、以後の試行にトークンが自動で付きます。本番で`/docs`を隠す設定は[FastAPIのSwagger UIを本番で隠す手順](https://www.issoh.co.jp/tech/details/2246/)を参照してください。端末から試す場合は次のとおりです。

```
# ログイン（フォーム形式で送る）
curl -s -X POST http://127.0.0.1:8000/token -d "username=alice&password=wonderland"

# 返った access_token を付けて呼ぶ（200）
curl -s http://127.0.0.1:8000/users/me -H "Authorization: Bearer 取得したaccess_token"

# トークンなしは 401 {"detail":"Not authenticated"}
curl -i http://127.0.0.1:8000/users/me
```

## リフレッシュトークンのローテーションと再利用検知をjtiで実装する

アクセストークンを15分で切る以上、更新の仕組みが要ります。ここで手を抜くと、14日有効なリフレッシュトークンが盗まれたときに止める手段がありません。

### typeクレームでアクセス用とリフレッシュ用の取り違えを防ぐ書き方

アクセス用とリフレッシュ用を同じ鍵・同じクレームで作ると、リフレッシュトークンを`Authorization`ヘッダーに入れても通ってしまいます。14日有効なトークンがAPIの鍵として使える状態です。`decode_token`が`expected_type`を照合しているのはこのためで、RFC 8725の3.11節（明示的な型付け）と3.12節（種類ごとに排他な検証規則）が求める対策にあたります。

より強く分けるなら、リフレッシュ用だけ`aud`を`example-api-refresh`のように変えるか、鍵を別にします。どちらにしても、検証側が「このルートではどの種類を受け付けるか」を必ず指定する点は同じです。

### 使用済みjtiの再提示で本人の全リフレッシュトークンを失効させる

`rotate`は、受け取ったリフレッシュトークンの`jti`を保存先から取り除き、新しい組を発行します。同じトークンは2回使えません。使用済みの`jti`が来たら、正規の利用者と攻撃者のどちらかが古い1本を持っていることになります。どちらが本物かはサーバーから区別できないので、その利用者のリフレッシュトークンを全て失効させ、再ログインさせます。

サンプルは保存先を辞書にしていますが、複数のワーカーやサーバーで動かすなら共有の保存先が要ります。Redisなら`jti`をキーにして有効期限14日で置き、`GETDEL`で取り出しと削除を1回で行うと、同時に届いた2回の更新を両方通す競合を防げます。ログアウトは、この保存先からその利用者の`jti`を消す処理です。

### test\_tokens.pyで期限切れ・改ざん・再利用の拒否を確かめるテスト

`tokens.py`はFastAPIに依存しないので、HTTPを通さずにテストできます。期限の境目、種類の取り違え、再利用の検知、`alg: none`の拒否を確かめます。

```
# test_tokens.py（JWT_SECRET_KEY を設定してから pytest で実行する）
from datetime import timedelta

import jwt
import pytest

import tokens


def test_refresh_cannot_be_used_as_access():
    pair = tokens.issue_pair("user:42")
    with pytest.raises(jwt.InvalidTokenError):
        tokens.decode_token(pair["refresh_token"], "access")


def test_expired_and_leeway():
    expired, _ = tokens.create_token("user:42", "access", timedelta(seconds=-60))
    with pytest.raises(jwt.ExpiredSignatureError):
        tokens.decode_token(expired, "access")
    recent, _ = tokens.create_token("user:42", "access", timedelta(seconds=-10))
    assert tokens.decode_token(recent, "access")["sub"] == "user:42"  # leeway 30秒以内


def test_reuse_revokes_all():
    first = tokens.issue_pair("user:7")
    second = tokens.rotate(first["refresh_token"])
    with pytest.raises(jwt.InvalidTokenError):
        tokens.rotate(first["refresh_token"])  # 使用済みの再提示
    with pytest.raises(jwt.InvalidTokenError):
        tokens.rotate(second["refresh_token"])  # 巻き添えで失効している


def test_alg_none_rejected():
    forged = jwt.encode({"sub": "admin", "type": "access"}, None, algorithm="none")
    with pytest.raises(jwt.InvalidAlgorithmError):
        tokens.decode_token(forged, "access")
```

同じ内容をPyJWT 2.15.1で実行し、4本の検査がすべて期待どおりに動くことを確認しました。`leeway=30`は、発行したサーバーと検証するサーバーの時計のずれを吸収するための幅です。10秒前に切れたトークンは通り、60秒前に切れたトークンは拒否されます。

## 自前のJWT発行を採用する条件とIdPのJWKS検証に任せる判断基準

ここまでのコードは、利用者とパスワードを自分のAPIが持つ場合の形です。すべてのFastAPIアプリがこれを書くべきではありません。

### HS256の共有鍵で足りる構成とRS256へ切り替えるべき構成の境目

HS256は、発行と検証に同じ鍵を使います。発行するのも検証するのも同じFastAPIアプリ1つなら、HS256で対応できる構成です。鍵は`openssl rand -hex 32`で32バイト以上にします。PyJWT 2.15.1は6バイトの鍵`"secret"`で署名すると`InsecureKeyLengthWarning`を出し、32バイト未満を推奨外と警告しました（RFC 8725の3.5節）。

検証するサービスが2つ以上になったら、RS256かES256に切り替えます。共有鍵のままだと、検証だけしたいサービスにも発行できる鍵を配ることになり、どこか1か所から漏れれば全体のトークンを偽造されます。公開鍵署名なら、検証側には公開鍵しか渡りません。

### Auth0やCognitoを使う場合にPyJWKClientで検証だけを書く構成

ログイン画面、パスワードの保管、多要素認証、パスワードの再設定までを自前で持つのは、JWTの発行より重い仕事です。会員数が数百を超える、社内のMicrosoft Entra IDでSSOしたい、多要素認証を求められている、のどれかに当てはまるなら、IdPに任せてFastAPIは検証だけを書きます。この場合`/token`も`tokens.py`の発行部分も要りません。

検証は、PyJWTの`PyJWKClient`でIdPが公開するJWKSのURLから公開鍵を取り、`decode`に渡す形になります（[PyJWTのAPI Reference](https://pyjwt.readthedocs.io/en/stable/api.html)）。`algorithms`・`audience`・`issuer`を指定する規則は自前の場合と同じです。トークンを即時に失効させたい要件があるなら、署名の検証だけでは足りないので、[トークンイントロスペクションとJWT方式の使い分け](https://www.issoh.co.jp/tech/details/16417/)で問い合わせ型を検討します。

### ブラウザでのトークン保存先とログアウトを即時に効かせたい場面

SPAからこのAPIを呼ぶとき、リフレッシュトークンを`localStorage`に置くのは避けます。XSSが1つでもあれば、14日有効なトークンがそのまま持ち出される危険があるためです。リフレッシュトークンは`HttpOnly`・`Secure`・`SameSite`を付けたCookieで受け渡し、アクセストークンの保存先はメモリだけにする構成です。Cookieを使うとCSRF対策とCORSの設定が要るので、[React×FastAPI連携のCORS設定](https://www.issoh.co.jp/tech/details/2242/)と合わせて決めます。

「権限を外したら即座にAPIを止めたい」「管理画面から特定の端末だけをログアウトさせたい」という要件が出た時点で、署名だけで完結するJWTの利点は薄れます。サーバー側のセッションに戻すか、IdPに寄せる方が素直です。既存システムの認証をどちらに寄せるか、SSOや多要素認証をどう組み込むかから相談したい場合は、[認証基盤・ID管理システム開発](https://www.issoh.co.jp/service/system/identity/)で受け付けています。

## よくある質問

FastAPIでJWT認証を実装するときによく出る質問に、2026年11月時点の版で答えます。

### python-joseで書いた既存のコードはすぐに移行すべきですか？

動いているなら急ぐ必要はありませんが、次に認証まわりを触るときに移します。python-joseは2025年5月の3.5.0以降リリースが無く、FastAPIの公式チュートリアルも0.111.1でPyJWTへ戻っています。移行は`from jose import jwt`を`import jwt`に、例外の`JWTError`を`jwt.InvalidTokenError`に替えるのが中心です。`decode`で`audience`を渡していなかった場合、PyJWTでは`aud`入りのトークンが拒否されるので、検証の設定もあわせて見直します。

### JWTの中身は暗号化されていますか？

暗号化されていません。署名は改ざんを検出するだけで、ペイロードはBase64URLで符号化されているだけなので誰でも読めます。メールアドレスや権限の一覧など、クライアントに見せたくない値は入れません。`sub`には内部IDを入れ、詳しい情報はAPI側でDBから引きます。JWTの構造と署名の仕組みは[JWTとは？構造・署名検証の仕組みの解説](https://www.issoh.co.jp/tech/details/13453/)で説明しています。

### WebSocketのエンドポイントでもJWTで認証できますか？

できます。ブラウザのWebSocket APIは任意のヘッダーを付けられないため、クエリパラメータか最初のメッセージでトークンを受け取り、`accept()`の前に`tokens.decode_token`で検証します。FastAPI 0.144.0では、セキュリティ用の依存がWebSocketにも対応しました。クエリに載せたトークンはアクセスログに残るので、寿命を短くするか、接続専用の短命トークンを別に発行します。

### ロールや権限による認可はJWTにどう載せればよいですか？

小さなアプリなら`scope`クレームに`"items:read items:write"`のような空白区切りの文字列を入れ、FastAPIの`Security`と`SecurityScopes`で照合します。手順の参照先は公式の[OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/)です。権限を頻繁に変える業務システムでは、トークンには`sub`だけを載せ、権限はリクエストごとにDBから引く方が、変更が15分待たずに効きます。

### アクセストークンの有効期限は何分にすればよいですか？

5〜15分が目安です。署名の検証だけで通すJWTは期限まで止められないので、権限の剥奪から反映までの最大の遅れがこの長さになります。リフレッシュトークンと組み合わせれば、利用者が再ログインを求められるのはリフレッシュトークンの期限（この記事では14日）が切れたときだけです。公式チュートリアルは30分の例ですが、更新の仕組みを持つなら短くする方が安全です。

## 関連記事

- [JWTとは？構造・署名検証の仕組みとセッション・OAuth/OIDCとの違いを実装視点で解説](https://www.issoh.co.jp/tech/details/13453/)：この記事のコードが前提にしているJWTの仕組みです。
- [リフレッシュトークンとは？トークンローテーションの仕組みと更新フロー・寿命設計を実装者向けに解説](https://www.issoh.co.jp/tech/details/16409/)：ローテーションと寿命設計の考え方です。
- [FastAPIのDependsの使い方｜依存性注入・yield・テストの差し替えをコードで解説](https://www.issoh.co.jp/tech/details/19720/)：get\_current\_userを組み立てる依存性注入の仕組みです。
- [FastAPIとは？最短の起動手順とasync/defの使い分け、0.162の変更点](https://www.issoh.co.jp/tech/details/3859/)：FastAPI単体の入門と起動手順です。
- [Bearer Token（ベアラートークン）とは？読み方・Authorization: Bearerの書き方とJWTとの違い](https://www.issoh.co.jp/tech/details/4150/)：Authorizationヘッダーの書式です。

---

出典: [FastAPIのJWT認証｜PyJWTで発行・検証・リフレッシュトークンをコードで実装](<https://www.issoh.co.jp/tech/details/19747/>)（株式会社一創）
