JWT徹底解説：FastAPIで純粋なPythonだけで認証を実装する


[![Group192.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/d2988d33-0926-4f48-9b32-af559befe767.png)](https://leapcell.io/)



**[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)**

# FastAPIでのJWTを用いた認証の実装

## はじめに
現代のウェブアプリケーション開発において、認証はシステムのセキュリティを確保する上で重要な要素です。JSON Web Token (JWT) は、JSONに基づくオープン標準 (RFC 7519) として、コンパクトでセキュアな特性から、分散型サイトのシングルサインオン (SSO) シナリオで広く利用されています。FastAPIはPythonベースの高性能なウェブフレームワークです。この記事では、与えられたJWT実装コードを用いて、FastAPIでJWTを使った認証を行う方法を紹介します。

## JWTの基本

### JWTとは？
JSON Web Token (JWT) は、ネットワークアプリケーション環境間でクレームを伝達するために設計されています。コンパクトでセキュアであり、分散型サイトのシングルサインオンシナリオに特に適しています。JWTのクレームは、認証されたユーザーのID情報をアイデンティティプロバイダとサービスプロバイダ間で伝達し、リソースサーバからリソースを取得するためによく使われ、またビジネスロジックで必要な他のクレーム情報を追加することもできます。このトークンは、直接認証に使用したり、暗号化したりすることができます。

### JWTの構成
JWTは3つのセグメント情報から構成されています。
- **ヘッダー**：通常、ファイルタイプと暗号化アルゴリズムを宣言します。
- **ペイロード**：JSON形式で送信するデータを記録します。
- **署名**：検証に使用する署名です。

### base64UrlEncodeメソッド
JWTを生成する際には、署名に `base64UrlEncode` メソッドが使用されます。このメソッドの基本的な機能は以下の通りです。
1. UTF-8でエンコードされた文字列 `s1` を入力します。
2. 文字列 `s1` をbase64でエンコードして文字列 `s2` を取得します。
3. `s2` の末尾に等号がある場合、末尾のすべての等号を削除します。
4. `s2` の中にプラス記号 (`+`) がある場合、すべてのプラス記号をマイナス記号 (`-`) に置き換えます。
5. `s2` の中にスラッシュ (`/`) がある場合、すべてのスラッシュをアンダースコア (`_`) に置き換えます。

以下は、このメソッドを実装するPythonコードです。
```python
import hmac
import base64
from hashlib import sha256
from urllib import parse as urlp

def b64url(str1):
    if type(str1) == str:
        return str(base64.b64encode(str1.encode('utf-8')), encoding="utf-8").strip('=').replace('+', '-').replace('/', '_')
    elif type(str1) == bytes:
        return str(base64.b64encode(str1), encoding="utf-8").strip('=').replace('+', '-').replace('/', '_')
    else:
        raise TypeError("The type of given argument must be string or bytes")
```

### HS256で暗号化されたJWTの生成
以下は、上記の `b64url` 関数を使用してHS256で暗号化されたJWTを生成するコードです。
```python
# Enter Your Infomation Here ...
header = b64url('{"alg":"HS256","typ":"JWT"}')
payload = b64url('{"sub":"1234567890","name":"John Doe","iat":1516239022}')
secret = 'happynewyear'.encode('utf-8')
# ###

sig = b64url(hmac.new(
    secret, (header + '.' + payload).encode('utf-8'), digestmod=sha256
).digest())

jwt = header + '.' + payload + '.' + sig
print(jwt)
```

## FastAPIでのJWTの統合

### 依存関係のインストール
始める前に、`fastapi` と `uvicorn` をインストールする必要があります。以下のコマンドを使用してインストールできます。
```bash
pip install fastapi uvicorn
```

### 実装手順

#### 1. FastAPIアプリケーションの作成
```python
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import hmac
import base64
from hashlib import sha256

app = FastAPI()

# Define the b64url function
def b64url(str1):
    if type(str1) == str:
        return str(base64.b64encode(str1.encode('utf-8')), encoding="utf-8").strip('=').replace('+', '-').replace('/', '_')
    elif type(str1) == bytes:
        return str(base64.b64encode(str1), encoding="utf-8").strip('=').replace('+', '-').replace('/', '_')
    else:
        raise TypeError("The type of given argument must be string or bytes")

# Secret key
SECRET_KEY = 'happynewyear'.encode('utf-8')

# Function to generate JWT
def generate_jwt():
    header = b64url('{"alg":"HS256","typ":"JWT"}')
    payload = b64url('{"sub":"1234567890","name":"John Doe","iat":1516239022}')
    sig = b64url(hmac.new(
        SECRET_KEY, (header + '.' + payload).encode('utf-8'), digestmod=sha256
    ).digest())
    return header + '.' + payload + '.' + sig

# Function to verify JWT
def verify_jwt(token: str):
    parts = token.split('.')
    if len(parts) != 3:
        return False
    header, payload, received_sig = parts
    new_sig = b64url(hmac.new(
        SECRET_KEY, (header + '.' + payload).encode('utf-8'), digestmod=sha256
    ).digest())
    return new_sig == received_sig

# Define the JWT verification dependency
class JWTBearer(HTTPBearer):
    def __init__(self, auto_error: bool = True):
        super(JWTBearer, self).__init__(auto_error=auto_error)

    async def __call__(self, request):
        credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
        if credentials:
            if not credentials.scheme == "Bearer":
                raise HTTPException(status_code=401, detail="Invalid authentication scheme.")
            if not verify_jwt(credentials.credentials):
                raise HTTPException(status_code=401, detail="Invalid or expired token.")
            return credentials.credentials
        else:
            raise HTTPException(status_code=401, detail="Invalid authorization code.")

```

#### 2. ログインインターフェイスの作成
```python
# Simulated login interface
@app.post("/login")
def login():
    token = generate_jwt()
    return {"access_token": token, "token_type": "bearer"}

```

#### 3. 保護されたインターフェイスの作成
```python
# Protected interface
@app.get("/protected", dependencies=[Depends(JWTBearer())])
def protected_route():
    return {"message": "This is a protected route."}

```

### アプリケーションの実行
上記のコードを `main.py` として保存し、ターミナルで以下のコマンドを実行してアプリケーションを起動します。
```bash
uvicorn main:app --reload
```

### インターフェイスのテスト
- **ログインインターフェイス**：`curl` やPostmanなどのツールを使用して、`http://localhost:8000/login` にPOSTリクエストを送信すると、JWTが返されます。
```bash
curl -X POST http://localhost:8000/login
```
- **保護されたインターフェイス**：返されたJWTを使用して、`http://localhost:8000/protected` にGETリクエストを送信し、リクエストヘッダーに `Authorization: Bearer <your_token>` を追加します。
```bash
curl -X GET http://localhost:8000/protected -H "Authorization: Bearer <your_token>"
```

## まとめ
以上の手順を通じて、FastAPIでJWTを使った認証を行う方法を学びました。JWTは、ユーザーIDを安全かつ便利に管理する方法を提供し、分散型システム間の認証をより効率的にします。実際のアプリケーションでは、必要に応じてJWTの生成と検証ロジックを調整することができます。例えば、有効期限を追加したり、クレームをカスタマイズしたりすることができます。

# [Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)

最後に、Pythonサービスのデプロイに最適なプラットフォームをおすすめします。**[Leapcell](https://leapcell.io/)**

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/0ae458f1-8f88-4cee-8d5e-5ef4156dea70.png)](https://leapcell.io/)


### 🚀 好きな言語で構築
JavaScript、Python、Go、またはRustで簡単に開発できます。

### 🌍 無制限のプロジェクトを無料でデプロイ
使用した分だけ料金がかかり、リクエストがなければ料金はかかりません。

### ⚡ 従量制、隠れたコストは一切ありません
アイドル料金はなく、シームレスなスケーラビリティがあります。

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/0ba0d4f2-1110-4784-b6c9-8addfed1285b.png)](https://leapcell.io/)


[📖 Explore Our Documentation](https://docs.leapcell.io/)  

🔹 Twitterでフォロー：[@LeapcellHQ](https://x.com/LeapcellHQ)