\ お問い合わせはこちら! /

django-allauthのデフォルト機能だけで高度な認証機能を実装する方法

  • django-allauthで認証を実装したい
  • まずは基本の実装方法が知りたい

Djangoはデフォルトでも認証機能を実装していますが、より高度な認証を行うにはdjango-allauthを使うことが一般的です。

  • メールアドレスによる認証
  • 二段階認証
  • ソーシャルアカウント連携
  • テンプレートカスタマイズ
  • 認証プロセスのカスタマイズ

とても便利なdjango-allauthですが、使うのが初めてだと「どこをどう設定したら良いのか」がわかりにくいのも事実です。

そこで本記事では、基本となる「django-allauthのデフォルト認証機能の実装」から初めて、定番のカスタマイズを行うための設定をご紹介します。

【基本編】デフォルトの認証機能を実装する

まずはdjango-allauthのデフォルト機能を使った認証機能の実装方法をご紹介します。

必要最小限の構成を理解することで、django-allauthへの理解を深めていきましょう。

django-allauthをインストール

次のコマンドでdjango-allauthをインストールします。

pip install django-allauth

settings.pyの設定

django-allauthに必要な設定を書き込んでいきましょう。

INSTALLED_APPS = [
    # allauth
    "django.contrib.sites",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
]

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
]

SITE_ID = 1

# ユーザー認証にメールアドレスを使用
ACCOUNT_AUTHENTICATION_METHOD = "email"
# ユーザー登録にメールアドレスを必須にする
ACCOUNT_EMAIL_REQUIRED = True
# ユーザー名の登録を不要にする
ACCOUNT_USERNAME_REQUIRED = False
# 登録後、メールアドレスに確認メールが送信される
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# 連続してログイン失敗できる回数を5回に制限
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5
# ログインがロックされた後に再試行できるまでの時間
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 300

主だった項目について少し詳しく解説します。

INSTALLED_APPS

INSTALLED_APPSについては以下の通りです。

アプリ名機能
django.contrib.sites複数のウェブサイトを1つのDjangoプロジェクトで管理するサイトフレームワーク
allauthdjango-allauthの基本機能
allauth.accountメールアドレスとパスワードによる認証機能
allauth.socialaccountソーシャルアカウント認証機能
INSTALLED_APPS

django-allauthはdjango.contrib.sitesを利用して、異なるドメインでの認証を管理します

AUTHENTICATION_BACKENDS

AUTHENTICATION_BACKENDSについては以下の通りです。

バックエンド名機能
django.contrib.auth.backends.ModelBackendDjangoのデフォルトの認証バックエンドで、
ユーザー名とパスワードによる認証を処理
allauth.account.auth_backends.AuthenticationBackenddjango-allauthが提供する認証バックエンドで、メールアドレスやソーシャルアカウントによる認証を処理
AUTHENTICATION_BACKENDS

SITE_ID

SITE_IDは、Djangoで複数のサイトを1つのデータベースで管理するために必要な変数です。django-allauthを使用する際には必須の設定になります。

詳しくは別記事で解説する予定です。(準備中)

urls.pyの設定

django-allauth標準で搭載されているURL群を使いたい場合には、次のように追加します。

urlpatterns = [
    # 追加
    path("accounts/", include("allauth.urls")),
]

これを追加するだけで、次のエンドポイントが自動的に用意されます。

  • /accounts/login/
    • ログインページ
  • /accounts/logout/
    • ログアウトページ
  • /accounts/signup/
    • サインアップページ
  • /accounts/password/change/
    • パスワード変更ページ
  • /accounts/password/reset/
    • パスワードリセットページ
  • /accounts/password/reset/done/
    • パスワードリセット完了ページ
  • /accounts/password/reset/key/
    • リセットキーを使用したパスワードリセットページ

これらを明示してURLを設定したい場合の方法は後述します。

メールの設定

Djangoにメール機能を付与しましょう。

本番環境など、実際にメールを送信したい場合には次のコードをsettings.pyに加えます。

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "your-email@gmail.com"
EMAIL_HOST_PASSWORD = "your-password"

もし開発環境用にとりあえず動かしたい場合には、上のコードの代わりに次のコードを書きます。

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

django-allauthでよく行われるカスタマイズ方法

ここからはよく行われるカスタマイズ方法をご紹介します。

django-allauthのデフォルト設定で使うことは少ないので、積極的にカスタマイズにも挑戦してみましょう。

ユーザー管理用のアプリを用意する

django-allauthはユーザー管理用のアプリを作らずとも使うことはできます。

ただ、アプリを作らずにユーザー管理しようとするとカスタマイズ性が落ちたり、コード全体の見通しがやや悪くなってしまう。

そこでacccountsアプリを作成して、ユーザーを独立したアプリとして管理する方法が一般的です。

詳しい実装方法は別記事で解説しました。

» 参考:【django-allauth】accountsアプリでユーザー認証機能を集約する方法

テンプレートHTML

django-allauth標準では、ごくシンプルなフォームだけが用意されています。

これらをカスタマイズする場合には、django-allauthのデフォルトのHTMLファイルをオーバーライドすることで対応します。

といっても難しいことはなく、次の各ディレクトリにHTMLファイルを配置するだけです。

.
└── templates/
    ├── account/
    │   ├── login.html
    │   ├── signup.html
    │   └── email/
    │       ├── email_confirmation_subject.txt
    │       └── email_confirmation_message.txt
    └── socialaccount/
        └── connections.html

名前が異なるとオーバーライドできないので、ディレクトリ名やファイル名は慎重に設定してください。

templates/account/login.html

{% extends "account/base.html" %}
{% load i18n %}
{% load account socialaccount %}

{% block head_title %}{% trans "ログイン" %}{% endblock %}

{% block content %}
<h1>{% trans "ログイン" %}</h1>

<form class="login" method="POST" action="{% url 'account_login' %}">
  {% csrf_token %}
  {{ form.as_p }}
  {% if redirect_field_value %}
  <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
  {% endif %}
  <button type="submit">{% trans "ログイン" %}</button>
  <a href="{% url 'account_reset_password' %}">{% trans "パスワードを忘れましたか?" %}</a>
</form>

{% endblock %}

アダプター

from allauth.account.adapter import DefaultAccountAdapter
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.urls import reverse_lazy


class CustomAccountAdapter(DefaultAccountAdapter):

    def is_open_for_signup(self, request):
        # サインアップを許可するかどうかを制御
        return True

    def save_user(self, request, user, form, commit=True):
        # ユーザー情報の保存方法をカスタマイズ
        user = super().save_user(request, user, form, commit=False)
        user.age = form.cleaned_data.get("age")
        user.weight = form.cleaned_data.get("weight")
        if commit:
            user.save()
        return user

    def clean_email(self, email):
        # メールアドレスのバリデーションをカスタマイズ
        if "example.com" not in email:
            raise ValidationError("Only example.com emails are allowed.")
        return email

    def get_login_redirect_url(self, request):
        # ログイン後のリダイレクトURLを指定
        path = reverse_lazy("profile-update", kwargs={"pk": request.user.pk})
        return path

    def get_logout_redirect_url(self, request):
        # ログアウト後のリダイレクトURLを指定
        return reverse_lazy("home")

    def confirm_email(self, request, email_address):
        # メール確認プロセスのカスタマイズ
        email_address.verified = True
        email_address.save()

    def add_message(self, request, level, message_template=None, message_context=None, extra_tags="", message=None):
        # カスタムメッセージの追加
        if not message:
            message = "Default custom message."
        messages.add_message(request, level, message)
ACCOUNT_ADAPTER = "your_project.adapter.CustomAccountAdapter"

このうち、特にis_open_for_signupとsave_userメソッドのカスタマイズの効果を説明します。

is_open_for_signupメソッド

このメソッドは、サインアップを許可するかどうかを制御します。

def is_open_for_signup(self, request):
    return True
  • デフォルトの動作
    • デフォルトでは常にTrueを返し、サインアップを常に許可します。
  • カスタマイズの効果:
    1. サインアップの一時停止
      • 例えば、return Falseとすると、サイト全体でサインアップを一時的に停止できます。
    2. 条件付きサインアップ
      • 特定の条件下でのみサインアップを許可できる。

条件付きサインアップの例は以下の通り。

def is_open_for_signup(self, request):
    return request.user.is_staff or datetime.now().weekday() < 5

これにより、スタッフユーザーまたは平日のみサインアップを許可できます。

save_userメソッド

このメソッドは、ユーザー情報の保存方法をカスタマイズします。

def save_user(self, request, user, form, commit=True):
    user = super().save_user(request, user, form, commit=False)
    user.age = form.cleaned_data.get("age")
    user.weight = form.cleaned_data.get("weight")
    if commit:
        user.save()
    return user
  • デフォルトの動作
    • デフォルトでは、基本的なユーザー情報(ユーザー名、メールアドレス、パスワードなど)のみを保存します。
  • カスタマイズの効果:
    1. 追加フィールドの保存
      • この例では、ageweightという追加フィールドをユーザーモデルに保存しています。
        これにより、サインアップ時に収集した追加情報をユーザープロファイルに含めることができます。
    2. データ加工
      • 保存前にデータを加工することができます。
        例えばuser.username = user.username.lower() # ユーザー名を小文字に変換
    3. 関連モデルの作成
      • ユーザー作成時に関連するモデルを同時に作成することができます。
      • 例えばProfile.objects.create(user=user, bio="New user")

django-allauth関連のパスをまとめる

allauthが扱うパスをaccount_patternsにまとめると、コードの可読性が上がります。

作成したリストはinclude()で呼び出しましょう。

from allauth.account import views as allauth_views
from django.contrib import admin
from django.urls import include, path

from . import views

# アカウント関連のURLパターン
account_patterns = [
    path("login/", views.CustomLoginView.as_view(), name="account_login"),
    path("signup/", views.CustomSignupView.as_view(), name="account_signup"),
    path("logout/", views.CustomLogoutView.as_view(), name="account_logout"),
    path("confirm-email/<str:key>/", views.CustomConfirmEmailView.as_view(), name="account_confirm_email"),
    path("password/reset/", views.CustomPasswordResetView.as_view(), name="account_reset_password"),
    path("password/change/", views.CustomPasswordChangeView.as_view(), name="account_change_password"),
]

urlpatterns = [
    path("", views.home, name="home"),
    path("admin/", admin.site.urls),
    path("accounts/", include((account_patterns, "account"))),
    path("blog/", include("apps.blog.urls", namespace="blog")),
]

この方法では、次のようなパターンが作成されます。

  • accounts/login/
  • accounts/signup/
  • accounts/logout/
  • accounts/confirm-email/<str:key>/
  • accounts/password/reset/
  • accounts/password/change/

また、URLの逆引きは次のように行います。

from django.urls import reverse

login_url = reverse("account:account_login")
signup_url = reverse("account:account_signup")
<a href="{% url 'home' %}">ホーム</a>
<a href="{% url 'account:account_login' %}">ログイン</a>
<a href="{% url 'account:account_signup' %}">サインアップ</a>

ログイン以外の書き方は次のとおりです。

  • account:account_login
  • account:account_signup
  • account:account_logout
  • account:account_confirm_email
  • account:account_reset_password
  • account:account_change_password

部分的にビューやテンプレートをオーバーライドする

一部のビューやテンプレートをオーバーライドする場合、urls.pyを少し工夫するとコードがコンパクトになります。

例えばログインビューだけをオーバーライドする場合は次の通りです。

urlpatterns = [
    path("accounts/", include("allauth.urls")),
    path("accounts/login/", CustomLoginView.as_view(), name="account_login"),
]

include("allauth.urls")によりすべてのdjango-allauthのエンドポイントを有効化しつつ、ログイン部分だけをカスタムビューに置き換えています。

また、複数のビューをオーバーライドする場合には認証関連のURLをinclude()関数でまとめてしまうのも一つの手です。

# アカウント関連のURLパターン
account_patterns = [
    path("login/", CustomLoginView.as_view(), name="account_login"),
    path("signup/", CustomSignupView.as_view(), name="account_signup"),
    path("logout/", CustomLogoutView.as_view(), name="account_logout"),
]

urlpatterns = [
    path("", views.home, name="home"),
    path("admin/", admin.site.urls),
    path("accounts/", include("allauth.urls")),
    path("accounts/", include(account_patterns)),
    path("blog/", include("apps.blog.urls", namespace="blog")),
]

これなら将来的に他のビューをカスタマイズしたい場合にも、account_patternspath()を追加すればOKです。

この記事が気に入ったら
フォローしてね!

シェア・記事の保存はこちら!

この記事を書いた人

karo@プログラマのアバター karo@プログラマ プログラマ

「書くことで人の役にたつ」をモットーに活動中。
本職はプログラマで、Pythonが得意。
基本情報技術者試験合格。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)