カテゴリー
アーカイブ

09.19
2025

FastAPI の認証機能を試してみる

  • LINE

はじめに

普段は Django で開発することが多いのですが、あるプロジェクトでリッチな画面が必要となり、FastAPI + Next.js を採用しました。

認証処理の実装方法が Django とは異なるため、今回は FastAPI で認証機能を試しながら理解を深めていきたいと思います。

※本記事は FastAPI の学習・理解を目的としたものであり、本番利用を前提としたものではない点にご留意ください。

 

環境構築

確認環境

  • uv: 0.8.18
  • Python: 3.13

uv を使って環境構築をしていきます。

$ uv init
$ uv venv
$ source .venv/bin/activate

FastAPI のパッケージを追加します。

$ uv add "fastapi[standard]"

サンプルのエンドポイントを用意し、簡易的な動作確認をします。

# main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}
$ uv run uvicorn main:app --reload

http://127.0.0.1:8000/docs にアクセスし、Swagger UI からエンドポイントを叩いてみます。

無事動いていることを確認できました。

 

実装と動作確認

ステップ1. ユーザーの取得

最初に、ユーザー情報を返すエンドポイントを用意し、ダミーの情報を固定で返すようにします。

# main.py
from typing import Annotated

from fastapi import Depends, FastAPI
from pydantic import BaseModel

app = FastAPI()


class User(BaseModel):
    username: str
    email: str | None = None
    full_name: str | None = None
    disabled: bool | None = None


async def get_current_user():
    return User(
        username="johndoe",
    )


@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
    return current_user

read_users_me の引数で current_user: Annotated[User, Depends(get_current_user)] のように指定し、実行時に自動的にユーザーを取得するようにしています。

まだ認証処理を入れていないため、そのまま実行できます。

ステップ2. 認証機能の追加

次に認証機能を追加します。

FastAPI では、OAuth2PasswordBearer を使うことで認証機能を実装できるようです。

# main.py
from typing import Annotated

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


class User(BaseModel):
    username: str
    email: str | None = None
    full_name: str | None = None
    disabled: bool | None = None


async def get_current_user(token: str = Depends(oauth2_scheme)):
    return User(
        username="johndoe",
    )


@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
    return current_user

get_current_user に token: str = Depends(oauth2_scheme) を追加し、ユーザーの取得にアクセストークンが必須となるようにしました。

この状態で Swagger UI を開くと、「Authorize」のボタンが追加され、GET /users/me は認証が必要な状態になっている(鍵マークが付いている)ことがわかります。

実際に実行してみると、 401(Unauthorized) が返ります。

ステップ3. アクセストークンの発行と利用

このままアクセストークンの発行処理と、アクセストークンからユーザーを取得する処理を仮実装していきます。

# main.py
from typing import Annotated

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


class User(BaseModel):
    username: str
    email: str | None = None
    full_name: str | None = None
    disabled: bool | None = None


class UserInDB(User):
    hashed_password: str


fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "fakehashedsecret",
        "disabled": False,
    },
}


def verify_password(plain_password: str, hashed_password: str) -> bool:
    # 本来は bcrypt や Passlib を使ってハッシュと照合する
    return "fakehashed" + plain_password == hashed_password


def get_user(db, username: str) -> UserInDB | None:
    if username in db:
        return UserInDB(**db[username])
    return None


def authenticate_user(username: str, password: str) -> User | None:
    user = get_user(fake_users_db, username)
    if not user:
        return None
    if not verify_password(password, user.hashed_password):
        return None
    return user


def decode_token(token):
    # 本来は JWT の検証やデコードを行う
    user = get_user(fake_users_db, token)
    if not user:
        return None
    return user


async def get_current_user(token: str = Depends(oauth2_scheme)):
    user = decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
        )
    return User(**user.model_dump(exclude={"hashed_password"}))


@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # 便宜上、ユーザー名をアクセストークンとして返す
    return {"access_token": user.username, "token_type": "bearer"}


@app.get("/users/me")
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
    return current_user

いくつか変更箇所があるため、概要を記載します。

・ダミーのDBとして、fake_users_db を用意しました。

・ログイン用のエンドポイント(POST /token)を追加しました。
 ・フォームデータは OAuth2PasswordRequestForm を使用して受け取っています。
 ・認証処理自体は authenticate_user の中で行っています。
 ・ユーザー名とパスワードからユーザーの存在チェックを行い、ユーザーが存在する場合はアクセストークンを生成して返します。
 ・便宜上、ユーザー名をアクセストークンとして返しています。
 ・verify_password 関数では、本来 bcrypt や Passlib を使ってパスワードのハッシュと照合しますが、今回は簡易的に 'fakehashed' を用いています。

・get_current_user で、アクセストークンからユーザーを特定して返すようにしました。
 ・アクセストークンからユーザーを特定する処理は decode_token の中で行っています。
 ・実際はアクセストークンの偽造や改ざんを防ぐための工夫が必要になります。

実際に動作を確認してみましょう。

「Authorize」からユーザー名とパスワード(今回の例では johndoe/secret)を入力してログインし、アクセストークンを取得します。

認証が通りました。この状態で GET /users/me を実行してみます。

リクエストヘッダーに "Authorization: Bearer {access_token}" が追加され、レスポンスが 200 で返っていることが分かります。

 

ここまでで基本的な認証フローは実装できました。しかし現状では、アクセストークンの偽造や改ざんは防げません(例えばヘッダーを書き換えてリクエストを送れば他のユーザーへのなりすましができてしまう)。

アクセストークンの偽造や改ざんを防ぐ手段として、JWT 形式でのアクセストークンの発行があります。

JWT形式のアクセストークンは署名つきで発行されるため、トークンの偽造や改ざんを検知することができます。

 

まとめ

FastAPI の標準機能だけで、簡単にベアラートークン方式の認証を入れられることが分かりました。

実装のポイントは以下の2つです。

・OAuth2PasswordBearer を使い、アクセストークンが必要なエンドポイントの Depends に指定する

・OAuth2PasswordRequestForm を使い、ログインおよびアクセストークン発行用のエンドポイントを実装する

 

次回は JWT 形式でアクセストークンを発行し、トークンの偽造や改ざんを防げる形にアップデートしていきます。

 

参考資料

"全員が技術者" ベイストリームは横浜発のPythonのプロフェッショナル集団です。
積極採用中!尖ったPythonエンジニアへの第一歩はこちらから