1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Djangoでログイン認証する

Posted at

この記事について

Djangoの認証機能の使い方メモで
以下記事の手順に多少追記したものです
https://qiita.com/ryo-keima/items/e6ce42bb4c11432ea829

Djangoの認証機能

公式ドキュメントが以下にあります
https://docs.djangoproject.com/ja/5.1/topics/auth/

仕組み

Djangoモデル (DB) にユーザー情報を保持しておいて、ユーザー名とパスワードで認証・認可する機能です。ユーザーモデルに権限情報を書いておけば、認証しているユーザーのプロパティとして簡単に取得できますので確実かつ簡単に扱えます

ログインするとセッションIDをユーザーブラウザ側のCookieに保持することでログイン状態を維持することができるという仕組みです

デフォルトではセッションCookieを使用しますが有効期限付きの永続Cookieに変更することも可能ですし、セッション情報をどこに保持するか設定したり、セッションタイムアウトを設定したりなどの詳しい設定もsettings.pyから指定可能です

できないこと

公式ドキュメントによると、Djangoの認証機能は一般的に求められる機能に限って提供するものであるので以下の機能については提供がないようです

・パスワード強度のチェック
・ログイン試行数の制限
・サードパーティに対する認証 (OAuthなど)
・オブジェクトレベルのパーミッション

上記についてはサードパーティのパッケージで実装する想定のようです

やり方

認証用のFormとViewを使うことで簡単に機能が実装できます

  1. ユーザーモデルをつくる
  2. UserCreationFormとCreateViewでユーザー登録機能をつくる
  3. AuthenticationFormとBaseLoginViewでログイン機能をつくる
  4. BaseLogoutViewでログアウト機能をつくる

完成品

完成したコードをgithubに置いています
https://github.com/haneya-studio/django_auth_sample

フォルダ構成は以下のようになりました

1. プロジェクトの作成

お試しで認証機能を付ける為のサンプルプロジェクトを作ります
既にアプリがあるならそちらで作ってもらっても大丈夫

1-1. プロジェクトをつくる

プロジェクトを作りたいディレクトリに移動してプロジェクトを作ります

terminal
django-admin startproject myapp .

1-2. migrateする

terminal
cd myapp
python manage.py migrate

1-3. 試しに起動

terminal
python manage.py runserver

http://127.0.0.1:8000

2. ユーザーモデルをつくる

2-1. ユーザーモデルの選択肢

認証する為にユーザー情報を持つ必要があります
ユーザー情報を保持するモデルとして以下の3つが選べます

・Djangoデフォルトのユーザーモデルを使う
・カスタムユーザーモデルをつくる (AbstractUser)
・カスタムユーザーモデルをつくる (AbstractBaseUser, PermissionsMixin)

デフォルトのユーザーモデル

django.contrib.auth.models.Userで定義されているUserモデルをそのまま使います

Djangoにはデフォルトのユーザーモデルが定義されていて、ユーザー情報を登録することができるように以下のFieldが使用できます

フィールド名 説明
username CharField ユーザー名(デフォルトでは一意制約あり)
email EmailField メールアドレス
first_name CharField 名(オプション)
last_name CharField 姓(オプション)
password CharField ハッシュ化されたパスワード
is_staff BooleanField 管理サイトにログイン可能か
is_active BooleanField ユーザーが有効かどうか
is_superuser BooleanField 管理者権限を持つか
last_login DateTimeField 最終ログイン日時
date_joined DateTimeField ユーザー登録日時

これでも問題はないようですが、後日変更を入れたくなるケースが想定されるのでカスタムユーザーモデルをつくっておくのが推奨されているようです

カスタムユーザーモデル (AbstractUser)

デフォルトのUserモデルにFieldを追加して使いたい場合にはAbstractUserを選択します。自由度は下がりますが、足す方向で良いならこれを選択する方が簡単にできるようです

カスタムユーザーモデル (AbstractBaseUser)

最低限の機能だけ継承してFieldの設計からやりたい場合はAbstractBaseUserとPermissionsMixinを継承して作成します。機能は継承元クラスに既にありますので、Field設計だけやれば完成です

また、Field構造に応じてユーザーオブジェクトの作成方法も変更しなければならないので、BaseUserManagerをoverrideしてcreate_userメソッドとcreate_superuserメソッドも変更してやります

2-2. 作成手順

Djangoアプリを作成

アカウント情報を持つDjangoアプリを追加します

terminal
python manage.py startapp accounts

setting.pyに追加

AUTH_USER_MODELで認証用のユーザーモデルに指定します

myapp/setting.py
INSTALLED_APPS = [
    ...
    'accounts'  # 追加
]

AUTH_USER_MODEL = "accounts.User"  # 認証に使うカスタムユーザーモデルを指定

カスタムユーザーモデルの作成

今回はAbstractBaseUserとPermissionsMixinを使って作成します

UserクラスにField構成を定義して
UserManagerにオブジェクト作成時の処理を書いています

accounts/models.py
from django.db import models
from django.contrib.auth.models import (BaseUserManager, AbstractBaseUser, PermissionsMixin)
from django.utils.translation import gettext_lazy as _

class UserManager(BaseUserManager):
    def _create_user(self, email, account_id, password, **extra_fields):
        email = self.normalize_email(email)
        user = self.model(email=email, account_id=account_id, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_user(self, email, account_id, password=None, **extra_fields):
        extra_fields.setdefault('is_active', True)
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(
            email=email,
            account_id=account_id,
            password=password,
            **extra_fields,
        )

    def create_superuser(self, email, account_id, password, **extra_fields):
        extra_fields['is_active'] = True
        extra_fields['is_staff'] = True
        extra_fields['is_superuser'] = True
        return self._create_user(
            email=email,
            account_id=account_id,
            password=password,
            **extra_fields,
        )

class User(AbstractBaseUser, PermissionsMixin):
    account_id = models.CharField(verbose_name=_("account_id"), unique=True, max_length=10)
    email = models.EmailField(verbose_name=_("email"), unique=True)
    first_name = models.CharField(verbose_name=_("first_name"), max_length=150, null=True, blank=False)
    last_name = models.CharField(verbose_name=_("last_name"), max_length=150, null=True, blank=False)
    birth_date = models.DateField(verbose_name=_("birth_date"), blank=True, null=True)
    is_superuser = models.BooleanField(verbose_name=_("is_superuer"), default=False)
    is_staff = models.BooleanField(verbose_name=_('staff status'), default=False)
    is_active = models.BooleanField(verbose_name=_('active'), default=True)
    created_at = models.DateTimeField(verbose_name=_("created_at"), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_("updateded_at"), auto_now=True)

    objects = UserManager()

    USERNAME_FIELD = 'account_id'  # ログイン時、ユーザー名の代わりにaccount_idを使用
    REQUIRED_FIELDS = ['email']  # スーパーユーザー作成時にemailも設定する

    def __str__(self):
        return self.account_id

マイグレーションする

terminal
python manage.py makemigrations
python manage.py migrate

adminページで編集できるようにする

accounts/admin.py
from django.contrib import admin
from django.contrib.auth.models import Group
from .models import User

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    pass

admin.site.unregister(Group)  # Groupは要らないので非表示にする

superuserをつくる

admin編集用のsuperuserをつくります
ID, passwordを求められるので入力します

terminal
python manage.py createsuperuser

adminページでユーザーを登録

作成できているか確認しましょう

terminal
python manage.py runserver

http://127.0.0.1:8000/admin

SUPERUSERでログインしたままにすると後のログイン動作確認でSUPERUSERでログインしている扱いになってしまいますのでログアウトしておきます

3. 画面をつくる

3-1. ユーザー登録画面をつくる

formをつくる

accounts/forms.py
from django.contrib.auth.forms import UserCreationForm
from .models import User

class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = (
            "account_id",
            "email",
            "first_name",
            "last_name",
            "birth_date",
        )

viewをつくる

indexとsignupのViewをつくります

accounts/views.py
from django.contrib.auth import login, authenticate
from django.views.generic import TemplateView, CreateView
from django.urls import reverse_lazy
from .forms import SignUpForm

class IndexView(TemplateView):
    """ ホームビュー """
    template_name = "index.html"

class SignupView(CreateView):
    """ ユーザー登録用ビュー """
    form_class = SignUpForm # 作成した登録用フォームを設定
    template_name = "accounts/signup.html" 
    success_url = reverse_lazy("accounts:index")  # ユーザー作成後のリダイレクト先ページ

    def form_valid(self, form):
        # ユーザー作成後にそのままログイン状態にする設定
        response = super().form_valid(form)
        account_id = form.cleaned_data.get("account_id")
        password = form.cleaned_data.get("password1")
        user = authenticate(account_id=account_id, password=password)
        login(self.request, user)
        return response

templateをつくる

共通部分をbase.htmlとしてつくります

templates/base.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- Bootstrap5のCDNを設定 -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
    crossorigin="anonymous"></script>
  <title>Account</title>
</head>

<body>
  <div class="container mx-auto text-center">
    {% block title %}
    {% endblock %}
    {% block content %}
    {% endblock %}
  </div>
</body>

</html>

最初の画面をつくります

userが唐突に登場していますが、これは現在アクセスしているユーザーのユーザーオブジェクトです。django.contrib.auth.context_processors.authがCookieを使ってなんか上手いことやってくれるようです。user.is_authenticatedはDjangoのSUPERUSERでログインしていてもTrueになってしまうので、実用のコードではもう少し記載が必要だと思います。その場合、デフォルトのユーザーモデルにuser.first_nameとuser.last_nameがないので「None None」と表示されてしまいます

templates/index.html
{% extends 'base.html' %}

{% block title %}
{% if user.is_authenticated %}
<div class="h1">MyPage</div>
{% else %}
<div class="h1">Main</div>
{% endif %}
{% endblock %}

{% block content %}
{% if user.is_authenticated %}
<div class="h2">Welcome {{ user.first_name }} {{ user.last_name }}</div>
{% else %}
<a href="{% url 'accounts:signup' %}" class="btn btn-primary">Signup</a>
{% endif %}
{% endblock %}

ユーザー登録画面をつくります

templates/accounts/signup.html
{% extends 'base.html' %}

{% block title %}
<div class="h1">Create account</div>
{% endblock %}

{% block content %}
<div>
  <br>
  <form method="POST">
    {% csrf_token %}
    {{ form.non_field_errors }}
    {% for field in form %}
    {{ field.label }}
    {{ field }}
    {{ field.errors }}
    <br>
    {% endfor %}
    <div class="mt-3">
      <button type="submit" class="btn btn-primary">Create</button>
      <a href="{% url 'accounts:index' %}" class="btn btn-secondary">Back</a>
    </div>
  </form>
</div>
{% endblock %}

テンプレートのディレクトリをsettings.pyに追加

myapp/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # templatesディレクトリを設定
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

urls.pyにviewを登録

myapp/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("accounts.urls")) # accounts.urls.pyを読み込むための設定を追加
]
accounts/urls.py
from django.urls import path
from . import views

app_name = "accounts"

urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path('signup/', views.SignupView.as_view(), name="signup"),
]

動かしてみる

terminal
python manage.py runserver

こんな感じで記入します

formsを利用して入力フォームをつくると入力バリデーションしてくれます

ユーザー登録すると以下のような表示になります

3-2. ログイン画面をつくる

サインアップして登録した情報でログインする画面をつくります

formをつくる

LoginFormを追加します

AuthenticationFormを継承してFormをつくると、modelは指定しなくてもデフォルトでsettings.pyのAUTH_USER_MODELで指定されているユーザーモデルを使って、ユーザー名とパスワードでログインするフォームが作成されます

from django.contrib.auth.forms import UserCreationForm, AuthenticationForm  # 追加

class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = (
            "account_id",
            "email",
            "first_name",
            "last_name",
            "birth_date",
        )

class LoginFrom(AuthenticationForm):
    pass

ログインIDとしてemailを使いたい場合は以下のようにバリデーション処理中に置き換えて扱えば出来るようです

emailとパスワードで認証するフォーム
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import get_user_model
from django import forms

User = get_user_model()

class EmailLoginForm(AuthenticationForm):
    username = forms.EmailField(label="Email", widget=forms.EmailInput(attrs={"autofocus": True}))

    def clean(self):
        email = self.cleaned_data.get("username")  # username フィールドを email として扱う
        password = self.cleaned_data.get("password")
        if email and password:
            try:
                user = User.objects.get(email=email)
                self.user_cache = user
                self.confirm_login_allowed(user)
            except User.DoesNotExist:
                raise forms.ValidationError("このメールアドレスのユーザーは存在しません。")
        return self.cleaned_data

viewをつくる

LoginViewを追加します

accounts/views.pyに追加
from django.contrib.auth.views import LoginView as BaseLoginView

class LoginView(BaseLoginView):
    form_class = LoginFrom
    template_name = "accounts/login.html"

templateをつくる

ログイン画面のテンプレートを作成します

templates/accounts/login.html
{% extends 'base.html' %}

{% block title %}
<div class="h1">Login</div>
{% endblock %}
{% block content %}
<form method="post">
    {% csrf_token %}
    {{ form.non_field_errors }}
    {% for field in form %}
    {{ field.label }}
    {{ field }}
    {{ field.errors }}
    <br>
    {% endfor %}
    <div class="mt-3">
        <button type="submit" class="btn btn-primary">Login</button>
        <a href="{% url 'accounts:index' %}" class="btn btn-secondary">Back</a>
    </div>
</form>
{% endblock %}

urlsにviewを登録

追加したloginページをルーティングに追加

from django.urls import path
from . import views

app_name = "accounts"

urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path('signup/', views.SignupView.as_view(), name="signup"),
    path('login/', views.LoginView.as_view(), name="login"),
]

settings.pyにリダイレクト先を登録

ログイン後にリダイレクトされるURLを指定します

settings.py
LOGIN_REDIRECT_URL = "accounts:index"

3-3. ログアウト画面をつくる

viewをつくる

LogoutViewを追加します

BaseLogoutViewを継承したViewクラスをつくっておいて、ボタンクリックから呼んでやればログアウト処理とログアウト後の画面表示をやってくれます

accounts/views.pyに追加
from django.contrib.auth.views import LogoutView as BaseLogoutView

class LogoutView(BaseLogoutView):
    success_url = reverse_lazy("accounts:index")

templateにログアウトボタンを追加

ログアウト処理はCSRF対策の関係でPOSTメソッドで送るのが推奨されているようです。その為、BaseLogoutViewはGETリクエストを405エラーではじいてしまいますので、ちょっと面倒ですがPOSTメソッドで呼ぶようにしてやります

templates/index.html
{% extends 'base.html' %}

{% block title %}
{% if user.is_authenticated %}
<div class="h1">MyPage</div>
{% else %}
<div class="h1">Main</div>
{% endif %}
{% endblock %}

{% block content %}
{% if user.is_authenticated %}
<div class="h2">Welcome {{ user.first_name }} {{ user.last_name }}</div>
<!-- 追加 -->
<form method="post" action="{% url 'accounts:logout' %}">
    {% csrf_token %}
    <button type="submit" class="btn btn-primary">ログアウト</button>
</form>
{% else %}
<a href="{% url 'accounts:signup' %}" class="btn btn-primary">Signup</a>
<a href="{% url 'accounts:login' %}" class="btn btn-primary">Login</a>
{% endif %}
{% endblock %}

urlsにviewを登録

追加したloginページをルーティングに追加

from django.urls import path
from . import views

app_name = "accounts"

urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path('signup/', views.SignupView.as_view(), name="signup"),
    path('login/', views.LoginView.as_view(), name="login"),
    path('logout/', views.LogoutView.as_view(), name="logout"),
]

settings.pyにリダイレクト先を登録

ログアウト後にリダイレクトされるURLを指定します

settings.py
LOGOUT_REDIRECT_URL = "accounts:login"

動かしてみる

これでログイン、ログアウト機能が追加できた筈です

terminal
python manage.py runserver

まとめ

めちゃめちゃ簡単に実装できます
Djangoすごい

レッツトライ

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?