3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでQRコード認証(TOTP)を実装する

Last updated at Posted at 2025-04-24

はじめに

Pythonを使ったwebアプリにQRcodeを使用したTOTP「Time-based One-Time Password(時間ベースのワンタイムパスワード)」認証を導入する手順を簡潔にまとめてみました。
まだ実装したことがない方の参考になれば嬉しいです。
(ユーザー登録・パスワード認証は実装されている前提で進めさせていただきます)

1. 必要なライブラリをインストール

pip install pyotp (ワンタイムパスワードを生成・検証するためのライブラリ)
pip install qrcode (QRコードを生成するライブラリ)

「requirements.txt」を使用する場合は、

requirements.txt
pyotp
qrcode
Dockerfile
COPY requirements.txt .
RUN pip install -r requirements.txt

2. カスタムユーザーモデルにtotp用フィールドの追加

Django には標準で User モデルが用意されています(django.contrib.auth.models.User)が、
認証周りを細かくカスタマイズしたい場合、AbstractBaseUser を継承して自分で定義する必要があります。

accounts/models.py
from django.contrib.auth.models import AbstractBaseUser

class User(AbstractBaseUser):

    # TOTP用のシークレットキーを保存するフィールドを追加
    totp_secret = models.CharField(max_length=32, blank=True, null=True)  

    # TOTP用のシークレットキーを生成する関数
    def generate_totp_secret(self):
        self.totp_secret = pyotp.random_base32()
        self.save()

    # ユーザーのTOTPコードを検証する関数
    def verify_totp(self, totp_code):
        if not self.totp_secret:
            return False
        totp = pyotp.TOTP(self.totp_secret)
        return totp.verify(totp_code)

3. QRcodeを表示する

各ユーザーがパスワード認証を突破したタイミングで、そのユーザーが持つシークレットキーから生成されたQRcodeを表示します。

accounts/views.py

from django.contrib.auth import get_user_model

User = get_user_model()

def generate_qr(request, username):
    user = User.objects.get(username=username)

    # まだTOTPが設定されていない場合、新しいシークレットを生成
    if not user.totp_secret:
        user.generate_totp_secret()

    totp = pyotp.TOTP(user.totp_secret)
    totp_uri = totp.provisioning_uri(name=user.username, issuer_name="YourAppName")

    # QRコードを生成
    qr = qrcode.make(totp_uri)
    buffer = BytesIO()
    qr.save(buffer, format="PNG")
    buffer.seek(0)

    return HttpResponse(buffer.getvalue(), content_type="image/png")
accounts/urls.py
from django.urls import path
from . import views

app_name = "accounts"

urlpatterns = [
    path('generate_qr/<str:username>/', views.generate_qr, name='generate_qr')
]
qrcode.html
<img id="qrCodeImage" src="" alt="Scan this QR code with Your MFA App">
myLogin.js
let qrImage = document.getElementById("qrCodeImage");
let qrUrl = `/accounts/generate_qr/${username}/`;
qrImage.src = qrUrl;

4. 入力codeのバリデーション

入力フォームなどから受け取ったcodeを検証し、認証が成功すればログイン処理に進めます。

accounts/views.py
def totpLogin(request):
    username = request.POST.get("username")
    user = User.objects.get(username=username)

    totp_code = request.POST.get("totp_code")
    totp_code = int(totp_code)

    if not user.verify_totp(totp_code):
        return HttpResponseForbidden("Invalid TOTP code")  # 認証失敗

    login(request, user)

おわりに

今回はTOTPの実装部分だけをぎゅっとまとめて紹介しました。
意外と少ないコードで実装できるんだなと驚きました。

実際に認証機能として実装する際は、
・フォーマットのバリデーション/エラーハンドリングの追加
・ユーザーが設定画面からTOTP機能をON/OFFできるようにする
・上記のタイミングでtotp_secretをリフレッシュする
・MFA App(認証端末)側でtotp_secretを削除してしまった場合のバックアップ方法
などが必要になるかと思います。

認証まわりに関連して、JWT(JSON Web Token)を導入する記事も書きましたので、ぜひそちらも合わせて御覧ください!
PythonでJWT認証を実装する

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?