【Django】Django標準の認証システム(Userモデル)でログイン機能を作る

Django

こんにちは、かつコーチです。

前回はN+1問題とselect_related・prefetch_relatedによるパフォーマンス改善を解説しました。

今回からは認証・セキュリティ分野に入り、Djangoに標準搭載されている認証システムを使ったログイン機能の実装方法を扱います。

Djangoはdjango.contrib.authというアプリが最初から用意されており、ログイン・ログアウト・パスワード管理まですぐに実装できます。

Djangoの認証システムとは?

django.contrib.authが提供するもの

認証(Authentication)とは、「このユーザーが誰であるか」を確認する仕組みのことです。

Djangoではdjango.contrib.authというアプリが標準で有効になっており、以下の機能がすぐに使えます。

  • Userモデル(ユーザー名・メールアドレス・パスワードなどを持つ標準モデル)
  • ログイン・ログアウトのビュー
  • パスワードのハッシュ化(元の文字列に戻せない形式に変換する処理)
  • パーミッション(権限)管理

startprojectした時点でINSTALLED_APPSdjango.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.pyLOGIN_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.pyLOGIN_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_requiredLoginRequiredMixinを使う
  • 新規登録にはUserCreationFormが標準で用意されている

次に読むべき記事

標準のUserモデルの制約に触れたところで、次はカスタムユーザーモデル(AbstractUser)を使った実装方法を解説します。

→ 次の記事:カスタムユーザーモデル(AbstractUser)を実装する

タグ: #Django #中級者向け #認証

タイトルとURLをコピーしました