こんにちは、かつコーチです。
前回はN+1問題とselect_related・prefetch_relatedによるパフォーマンス改善を解説しました。
今回からは認証・セキュリティ分野に入り、Djangoに標準搭載されている認証システムを使ったログイン機能の実装方法を扱います。
Djangoはdjango.contrib.authというアプリが最初から用意されており、ログイン・ログアウト・パスワード管理まですぐに実装できます。
Djangoの認証システムとは?
django.contrib.authが提供するもの
認証(Authentication)とは、「このユーザーが誰であるか」を確認する仕組みのことです。
Djangoではdjango.contrib.authというアプリが標準で有効になっており、以下の機能がすぐに使えます。
Userモデル(ユーザー名・メールアドレス・パスワードなどを持つ標準モデル)- ログイン・ログアウトのビュー
- パスワードのハッシュ化(元の文字列に戻せない形式に変換する処理)
- パーミッション(権限)管理
startprojectした時点でINSTALLED_APPSにdjango.contrib.authが含まれているため、追加のインストール作業なしで使い始められます。
なぜ自前で実装しないのか
パスワードの保存を自分で実装すると、ハッシュ化のアルゴリズム選定やソルト(同じパスワードでもハッシュ値を変える仕組み)の扱いなど、セキュリティ上の考慮点が非常に多くなります。
Djangoの標準機能は、業界標準のセキュリティ対策が最初から組み込まれているため、認証機能は基本的に自前実装せず標準機能に乗るのが定石です。
ログイン機能の実装手順
手順1:URLの設定
Djangoの認証ビューはdjango.contrib.auth.urlsに用意されているため、includeするだけでログイン・ログアウトのURLが使えるようになります。
# urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
]
この1行だけで、/accounts/login/や/accounts/logout/といったURLが自動的に用意されます。
手順2:ログイン用テンプレートの作成
標準のログインビューはregistration/login.htmlというテンプレートを探しにいくため、その場所にファイルを作成します。
<!-- templates/registration/login.html -->
{% extends "base.html" %}
{% block content %}
<h2>ログイン</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">ログイン</button>
</form>
{% endblock %}
{{ form.as_p }}と{% csrf_token %}を書くだけで、ユーザー名とパスワードの入力欄が表示されます。
手順3:ログイン後のリダイレクト先を指定する
settings.pyにLOGIN_REDIRECT_URLを指定しておくと、ログイン成功後の遷移先を制御できます。
# settings.py
LOGIN_REDIRECT_URL = "article_list" # ログイン成功後に遷移する画面のURL名
LOGOUT_REDIRECT_URL = "article_list" # ログアウト後に遷移する画面のURL名
これを設定し忘れると、ログイン成功後に/accounts/profile/という存在しないURLへ飛ばされてしまい、404エラーになります。
ユーザー登録機能を追加する
UserCreationFormを使う
ログイン機能とセットで必要になるのが、新規ユーザー登録の画面です。DjangoにはUserCreationFormという標準フォームが用意されています。
# views.py
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.views.generic import CreateView
class SignUpView(CreateView):
form_class = UserCreationForm
success_url = reverse_lazy("login")
template_name = "registration/signup.html"
# urls.py
from .views import SignUpView
urlpatterns = [
# ...
path("accounts/signup/", SignUpView.as_view(), name="signup"),
]
UserCreationFormは、ユーザー名・パスワード・パスワード(確認用)の3項目を持ち、パスワードのハッシュ化も自動で行ってくれます。
ログイン状態の判定と保護
@login_requiredでビューを保護する
ログインしていないユーザーからのアクセスを防ぎたいビューには、@login_requiredデコレータを使います。
from django.contrib.auth.decorators import login_required
@login_required
def article_create(request):
# ログインしていないユーザーは自動的にログイン画面へリダイレクトされる
...
クラスベースビューの場合はLoginRequiredMixinを継承します。
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
class ArticleCreateView(LoginRequiredMixin, CreateView):
model = Article
fields = ["title", "body"]
login_url = "/accounts/login/"
login_urlを省略すると、settings.pyのLOGIN_URL(デフォルトは/accounts/login/)が使われます。
テンプレートでログイン状態を出し分ける
テンプレートではrequest.user.is_authenticatedでログイン状態を判定できます。
{% if request.user.is_authenticated %}
<p>ようこそ、{{ request.user.username }}さん</p>
<a href="{% url 'logout' %}">ログアウト</a>
{% else %}
<a href="{% url 'login' %}">ログイン</a>
{% endif %}
request.userは、未ログイン時にはAnonymousUserというオブジェクトになるため、is_authenticatedはどちらの状態でも安全に呼び出せます。
つまずきやすいポイント:ログイン画面が用意したテンプレートを見てくれない
テンプレートの配置場所の間違い
筆者は最初、テンプレートをtemplates/login.htmlに置いてしまい、TemplateDoesNotExistエラーに悩まされました。
❌ Before:templatesフォルダ直下にテンプレートを置く
templates/
login.html # ← ここに置いても認識されない
TemplateDoesNotExist at /accounts/login/
registration/login.html
Djangoの標準LoginViewは、registration/login.htmlというサブフォルダ込みのパスを探しにいく仕様になっています。
✅ After:registrationフォルダを作ってその中に置く
templates/
registration/
login.html # ← 正しい配置場所
signup.html
registration/というフォルダ名はdjango.contrib.auth側で決め打ちになっているため、任意の名前には変更できません。
エラーメッセージに表示されたregistration/login.htmlというパスをそのままフォルダ構成に反映すれば解決します。
応用:カスタムユーザーモデルへの布石
標準のUserモデルには「ユーザー名は必須、メールアドレスは任意」といった仕様上の制約があります。
メールアドレスをログインIDにしたい、プロフィール画像を持たせたいといった要件が出てきた場合は、AbstractUserを継承したカスタムユーザーモデルへの切り替えを検討します。
ただしカスタムユーザーモデルへの切り替えは、プロジェクトの最初のマイグレーション前に行う必要があるという大きな制約があるため、次の記事で詳しく解説します。
まとめ
この記事のポイント
- Djangoには
django.contrib.authという認証アプリが標準搭載されている include("django.contrib.auth.urls")でログイン・ログアウトのURLがすぐ使える- ログインテンプレートは
registration/login.htmlという決まった場所に置く必要がある - ビューの保護には
@login_requiredやLoginRequiredMixinを使う - 新規登録には
UserCreationFormが標準で用意されている
次に読むべき記事
標準のUserモデルの制約に触れたところで、次はカスタムユーザーモデル(AbstractUser)を使った実装方法を解説します。
→ 次の記事:カスタムユーザーモデル(AbstractUser)を実装する