こんにちは!クライアント開発チームの山中です。
ビザスクでは認証にAuth0を使用しており、Auth0による認証の仕組みを調べる機会があったので、せっかくならテックブログにまとめよう!と思い立ち、Auth0を使った実装方法についてまとめてみました!
JWT認証とは
JWT(JSON Web Tokenの略、読み方はジョット)認証は、トークンベースの認証方式です。従来のcookieとDB側のセッション情報を使ったセッションベース認証とは異なり、API側でユーザーのセッション情報を保持しません。
従来のセッション認証との違い
| 項目 | セッション認証 | JWT認証 |
|---|---|---|
| 状態管理 | ステートフル(サーバーがセッション情報を保持) | ステートレス(サーバーがセッション情報を保持しない) |
| スケーラビリティ | セッション情報の共有が必要 | セッション情報の共有が不要 |
| セキュリティ | セッションID漏洩リスク | トークン漏洩リスク |
JWTの仕組み
JWTは事前に発行されたトークンをフロントエンドで保存しておき、リクエストのたびにそのトークンを送付します。API側では、そのJWTが正しいかどうかを毎回検証することで、認証されたユーザーからのリクエストかどうかを判定します。
JWTの構造
JWTは以下の3つの部分から構成されています
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 // ヘッダー . eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 // ペイロード . TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ // 署名
JWT認証の方式
どちらの方式でも、ヘッダーとペイロードを元に鍵を使って署名が正しいかどうかを検証します。
共通鍵方式
1. ログイン成功 → サーバーで秘密鍵を使ってJWTに署名 2. リクエスト時 → JWTをヘッダーに付与 3. 検証時 → サーバー側で同じ秘密鍵を使って署名を検証
この方式では、JWT発行側と検証側が同じ秘密鍵を共有している必要があります。
公開鍵方式
Auth0では公開鍵方式が採用されています
1. ログイン成功 → Auth0が秘密鍵を使ってJWTに署名 2. リクエスト時 → JWTをヘッダーに付与 3. 検証時 → Auth0から公開鍵を取得し、署名を検証
この方式では、JWT発行側(Auth0)が秘密鍵を管理し、検証側(アプリケーション)は公開鍵のみで検証できます。
Auth0における認証フロー
Auth0では Authorization Code Flow が使用されます。以下がその流れです:
認証フローの詳細
1. ログインボタンクリック ↓ 2. /authorize エンドポイントへリダイレクト (コールバック先もクエリパラメーターで指定) ↓ 3. Auth0がユーザーをログイン画面へリダイレクト ↓ 4. ユーザーがログイン情報を入力・送信 ↓ 5. 指定したコールバック先にcodeパラメーター付きでリダイレクト ↓ 6. /oauth/token にcodeを渡してトークンを取得
この流れにより、セキュアな認証が実現されています。
auth0-spa-jsを使った実装
実際の実装においては、Auth0が提供しているライブラリを使用することで、1から実装することなく導入することができます。
実際の実装方法を見ていきましょう。
フロントエンド実装
Auth0Clientの初期化
import { Auth0Client } from '@auth0/auth0-spa-js'; const auth0 = new Auth0Client({ domain: '<AUTH0_DOMAIN>', clientId: '<AUTH0_CLIENT_ID>', authorizationParams: { redirect_uri: '<MY_CALLBACK_URL>' } });
ログイン処理
ログインボタンが押されたときの処理
// ユーザーを /authorize エンドポイントへリダイレクト
auth0.loginWithRedirect()
この関数により、ユーザーはAuth0のユニバーサルログイン画面へリダイレクトされます。
コールバック処理
ユーザーがログイン後、コールバックページで以下の処理を実行
// コールバック処理(code → token変換 & キャッシュ保存)
auth0.handleRedirectCallback()
handleRedirectCallback関数の内部では以下の処理が行われています
https://github.com/auth0/auth0-spa-js/blob/main/src/Auth0Client.ts#L486C16-L486C38
- クエリパラメーターからcode、stateなどを取得
- codeを使ってtokenを取得
- tokenを保存
デフォルトではtokenはブラウザ上のメモリに保存され、リロードなどで失われます。
保存先をlocalStorageに指定することもでき、その場合はtokenを永続化されます。
https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options
トークンの取得と利用
// キャッシュからトークンを取得 const token = await auth0.getTokenSilently(); // APIリクエスト時にヘッダーに付与 const response = await fetch('/api/protected', { headers: { 'Authorization': `Bearer ${token}` } });
Auth0のActionsを設定することで、JWTのペイロードにユーザーIDなどをカスタムクレームとして含めることも可能です。
サーバーサイド実装
Auth0は公開鍵認証方式でJWTを検証します
1. Auth0から公開鍵を取得 ↓ 2. 公開鍵を使ってJWTを認証 ↓ 3. ペイロードをデコードして、カスタムクレームからユーザーID等を取得
実装例(Python + FastAPI)
Claudeで生成したものなので、動作する保証はないですが、、
import jwt import requests from fastapi import FastAPI, Depends, HTTPException from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials app = FastAPI() security = HTTPBearer() # Auth0設定 AUTH0_DOMAIN = "YOUR_DOMAIN" AUTH0_API_AUDIENCE = "YOUR_API_IDENTIFIER" def get_public_key(token): """JWTのkidに基づいて適切な公開鍵を取得""" # JWTヘッダーからkid(Key ID)を取得 unverified_header = jwt.get_unverified_header(token) kid = unverified_header.get("kid") # JWKSから対応する公開鍵を取得 jwks_url = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json" response = requests.get(jwks_url) jwks = response.json() for key in jwks["keys"]: if key["kid"] == kid: return key def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): """JWT検証""" try: # 公開鍵を取得してJWTを検証 public_key = get_public_key() token = credentials.credentials payload = jwt.decode( token, jwt.algorithms.RSAAlgorithm.from_jwk(public_key), algorithms=["RS256"], audience=AUTH0_API_AUDIENCE, issuer=f"https://{AUTH0_DOMAIN}/" ) return payload except Exception: raise HTTPException(status_code=401, detail="Invalid token") @app.get("/protected") async def protected_endpoint(current_user=Depends(verify_token)): """認証が必要なエンドポイント""" return { "message": "Hello, authenticated user!", "user_id": current_user.get("your_app/user_id") # Auth0側で指定したカスタムクレーム }
まとめ
JWT認証とAuth0を使った実装について説明しました。
Auth0を導入することで、認証周りの複雑な実装から解放され、より本質的な機能開発に集中できるようになります。
セキュリティ面でも専門性の高いAuth0に任せることで、安全性を高めることができます。
今回の実装方法が、JWT認証やAuth0の導入を検討されている方の参考になれば幸いです!
ビザスクではエンジニアの仲間を募集しています! 少しでもビザスク開発組織にご興味を持たれた方は、ぜひ一度カジュアルにお話ししましょう! recruit.visasq.co.jp