- 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プロジェクトで管理するサイトフレームワーク |
allauth | django-allauthの基本機能 |
allauth.account | メールアドレスとパスワードによる認証機能 |
allauth.socialaccount | ソーシャルアカウント認証機能 |
django-allauthはdjango.contrib.sites
を利用して、異なるドメインでの認証を管理します。
AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDSについては以下の通りです。
バックエンド名 | 機能 |
---|---|
django.contrib.auth.backends.ModelBackend | Djangoのデフォルトの認証バックエンドで、 ユーザー名とパスワードによる認証を処理 |
allauth.account.auth_backends.AuthenticationBackend | django-allauthが提供する認証バックエンドで、メールアドレスやソーシャルアカウントによる認証を処理 |
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を返し、サインアップを常に許可します。
- カスタマイズの効果:
- サインアップの一時停止
- 例えば、
return False
とすると、サイト全体でサインアップを一時的に停止できます。
- 例えば、
- 条件付きサインアップ
- 特定の条件下でのみサインアップを許可できる。
- サインアップの一時停止
条件付きサインアップの例は以下の通り。
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
- デフォルトの動作
- デフォルトでは、基本的なユーザー情報(ユーザー名、メールアドレス、パスワードなど)のみを保存します。
- カスタマイズの効果:
- 追加フィールドの保存
- この例では、
age
とweight
という追加フィールドをユーザーモデルに保存しています。
これにより、サインアップ時に収集した追加情報をユーザープロファイルに含めることができます。
- この例では、
- データ加工
- 保存前にデータを加工することができます。
例えばuser.username = user.username.lower() # ユーザー名を小文字に変換
- 保存前にデータを加工することができます。
- 関連モデルの作成
- ユーザー作成時に関連するモデルを同時に作成することができます。
- 例えば
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_patterns
にpath()
を追加すればOKです。
コメント