【初めてのDjango】(発展編 #03)django-allauthで認証機能を実装する

本記事では「初めてのDjango(発展編)」として、初めてDjangoというフレームワークを使用してWebアプリケーションを作ってみたい!という方向けに、簡単なアプリケーションを作成しながら基本的な知識をご紹介していきます。

本シリーズの全容はこのようになっております。

今回の内容

・djangoの認証機能の種類について
・django-allauthを用いた認証機能の実装

目次

django-allauthとは

Djangoでユーザー認証機能(ログイン機能)を実装する場合、デフォルトで用意されている django.contrib.auth を使用して実現することができます。

しかし今回は django-allauth というDjango用のパッケージを使用して認証機能を実装していきます。メリットとして、

  1. 簡単に実装できる
  2. ソーシャル認証機能までサポートされている

が挙げられます。ソーシャル認証機能とは、TwitterやFacebook、GitHubなどのアカウントを使用してログインできる機能のことです。サポートされている会社は20社以上に及びます。

その他さまざまな機能があるのにも関わらず、無料で使用することができます。

認証機能の実装

ここから実際に、django-allauthを使用して認証機能を実装していきます。

パッケージのインストール

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

仮想環境に入っていることを確認しましょう。

(venv_cafe)$ pip install django-allauth

プロジェクト設定ファイルの編集

django-allauth をDjangoで使用できるために settings.py を下記のように編集する必要があります

# ... 略 ...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles', 

    'cafe_app.apps.CafeAppConfig', 
    'accounts.apps.AccountsConfig', 
    
    'django.contrib.sites',    # 追加
    'allauth',                 # 追加
    'allauth.account',         # 追加
    'allauth.socialaccount',   # 追加
]

# ... 略 ...

# django.contrib.sites用のサイト識別IDを設定
SITE_ID = 1

# 認証バックエンドの設定
AUTHENTICATION_BACKENDS = (
    'allauth.account.auth_backends.AuthenticationBackend',   # 一般ユーザー用(メールアドレス認証)
    'django.contrib.auth.backends.ModelBackend',    # 管理サイト用(ユーザー名認証)
)

# 認証方式を「メールアドレス」に設定
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_USERNAME_REQUIRED = False    # ユーザー名は使用しない

# サインアップにメールアドレス確認を挟むように設定
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_EMAIL_REQUIRED = True

# ログイン / ログアウト後のリダイレクト先を設定
LOGIN_REDIRECT_URL = 'cafe_app:index'
ACCOUNT_LOGOUT_REDIRECT_URL = 'account_login'

# 「ログアウト」を一回クリックしただけでログアウトできるように設定
ACCOUNT_LOGOUT_ON_GET = True

# django-allauthが送信するメールの件名に自動付与される接頭辞を空にする設定
ACCOUNT_EMAIL_SUBJECT_PREFIX = ''

# デフォルトのメール送信元の設定
DEFAULT_FROM_EMAIL = '<任意の送信元>'
補足
認証バックエンド

djangoの認証は、認証用のバックエンド(認証をテストするクラス)によって行われます。AUTHENTICATION_BACKENDSに配列として渡すことでバックエンドを追加することができる。認証時には追加した順に認証を行い、もし成功すれば認証したユーザーを返す。

デフォルトは django.contrib.auth.backends.ModelBackend のみ。

サインアップにメールアドレス確認をはさむ

上記のように記述することで、

  1. ユーザー登録(仮登録)
  2. メールが送信される
  3. 送信されたメールのリンクをクリック
  4. ユーザー登録(本登録)

という流れにすることができます。メールアドレスの認証をはさむことで本人確認を行うことができます。

ログアウト後の挙動

django-allauthのデフォルトの設定では、「ログアウト」ボタンを押した後に

  1. ログアウト画面が表示される
  2. 「ログアウト」をクリック
  3. ログアウト

というくどい流れになっております。「ログアウト」を一回クリックしただけですぐにログアウトできるように、「ACCOUNT_LOGOUT_ON_GET = True」と記述しています。

ルーティングの設定

プロジェクト側urls.py を下記のように編集します。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls), 
    path('', include('cafe_app.urls')), 
    path('accounts/', include('allauth.urls')), 
]

このように記述することで、https://<ホスト名>/accounts/~ というURLにアクセスした際に、django-allauthがデフォルトで持っている urls.py に処理をお願いしています。

django-allauthの持つデフォルトのルーティングは下記のようになります。

URL処理内容
/accounts/signup/サインアップ(エントリー画面表示)
/accounts/confirm-email/サインアップ(メール送信)
/accounts/confirm-email/<キー>/サインアップ(確定)
/accounts/login/ログイン
/accounts/logout/ログアウト
/accounts/password/reset/パスワードリセット(エントリー画面表示)
/accounts/password/reset/done/パスワードリセット(メール送信)
/accounts/password/reset/key/<キー>-set-password/パスワードリセット(パスワード設定)
/accounts/password/reset/key/done/パスワードリセット(確定)

django-allauthのテンプレートを上書きする

通常、新規ページを作成したい場合は、各アプリケーションの templates/ ディレクトリにHTMLファイルを作成していました。

しかしdjango-allauthにはデフォルトのテンプレートが備わっております(allauth/templates/accountにあります)。なので独自に新しくテンプレートを作成しなくても動作はするのですが、今回は上書きすることで独自のテンプレートを作成します。

ちなみにデフォルトのサインアップ画面はこんな感じです。

「accounts/templates/account」ディレクトリを新規作成し、その配下に上書きしたいデフォルトテンプレートと同名のファイルを作成することで、独自のテンプレートを作成することができます。

「accounts/templates/account/」ディレクトリに下記の8つのHTMLファイルを作成し、それぞれ下記の内容をコピペしてください。

多いですが頑張りましょう、、、。

※参考までに完成後の画像を載せておりますが、後述するCSSの内容を加えた後のイメージです。

サインアップ(エントリー画面)
{% extends 'base.html' %}

{% load static %}

{% block title %}サインアップ{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>ユーザー登録</h2>
        <p>すでにアカウントをお持ちであれば、こちらから<a href="{% url 'account_login' %}"><span class="accounts-link">ログイン</span></a>してください。</p>
    </div>
    <div class="wrapper-accounts-form">
        <form method="post" action="{% url 'account_signup' %}" class="form-accounts">
            {% csrf_token %}

            <div class="form-content">
                <label>Email</label>
                <input type="email" name="email" autocomplete="email" required id="id_email">
            </div>
            <div class="form-content">
                <label>Password</label>
                <input type="password" name="password1" autocomplete="new-password" required id="id_password1">
            </div>
            <div class="form-content">
                <label>Password(確認用)</label>
                <input type="password" name="password2" autocomplete="new-password" required id="id_password2">
            </div>

            {% if redirect_field_value %}
                <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}">
            {% endif %}

            <button class="accounts-btn" type="submit">登録</button>
        </form>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
サインアップ(メール送信)
{% extends 'base.html' %}

{% load static %}

{% block title %}メールアドレスの確認{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>メールアドレスを確認してください</h2>
        <p>確認のメールを送信しました。</p>
        <p>
            メールに記載されたリンクをクリックして、ユーザー登録を完了させてください。<br>
            数分経っても確認のメールが届かない場合はご連絡ください。
        </p>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
サインアップ(確定)
{% extends 'base.html' %}

{% load static %}

{% block title %}サインアップの確定{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>ユーザー登録</h2>
    </div>
    
    {% if confirmation %}
    <div class="wrapper-accounts-form text-align-center">
            <p class="notion">ユーザー登録を確定するには、以下のボタンを押してください。</p>
            <form method="post" action="{% url 'account_confirm_email' confirmation.key %}" class="form-accounts">
                {% csrf_token %}
    
                <button class="accounts-btn" type="submit">確定</button>
            </form>
        </div>
    {% else %}
        {% url 'account_email' as email_url %}

        <div class="text-align-center">
            <p>リンクの有効期限が過ぎています。<a href="{{ email_url }}">再申請</a>.</p>
        </div>
    {% endif %}

    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
ログイン画面
{% extends 'base.html' %}

{% load static %}

{% block title %}ログイン{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>ログイン</h2>
    </div>
    <div class="wrapper-accounts-form">
        <form method="post" action="{% url 'account_login' %}" class="form-accounts">
            {% csrf_token %}

            <div class="form-content">
                <label>Email</label>
                <input type="email" name="login" autocomplete="email" required id="id_login">
            </div>
            <div class="form-content">
                <label>Password</label>
                <input type="password" name="password" autocomplete="current-password" required id="id_password">
            </div>
            <div class="radio-btn">
                <input type="checkbox" name="remember" id="id_remember">
                <label>ログイン状態を保持する</label>
            </div>

            {% if redirect_field_value %}
                <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}">
            {% endif %}

            <button class="accounts-btn" type="submit">ログイン</button>
            <p class="notion"><a href="{% url 'account_reset_password' %}">パスワードをお忘れですか?</a></p>
        </form>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
パスワードリセット(エントリー)
{% extends 'base.html' %}

{% load static %}

{% block title %}パスワードリセット{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>パスワードリセット</h2>

        {% if user.is_authenticated %}
            {% include "account/snippets/already_logged_in.html" %}
        {% endif %}

        <p>パスワードリセット用のメールを送信します。</p>
    </div>
    <div class="wrapper-accounts-form">
        <form method="post" action="{% url 'account_reset_password' %}" class="form-accounts">
            {% csrf_token %}

            <div class="form-content">
                <label>Email</label>
                <input type="email" name="email" placeholder="メールアドレス" autocomplete="email" required id="id_email">
            </div>
            <button class="accounts-btn" type="submit">送信</button>
        </form>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
パスワードリセット(メール送信)
{% extends 'base.html' %}

{% load static %}

{% block title %}パスワードリセット完了{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>パスワードリセット</h2>

        {% if user.is_authenticated %}
            {% include "account/snippets/already_logged_in.html" %}
        {% endif %}

        <p>パスワードリセット用のメールを送信しました。</p>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
パスワードリセット(パスワード設定)
{% extends 'base.html' %}

{% load static %}

{% block title %}パスワードリセット{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>{% if token_fail %}不正トークン{% else %}パスワードリセット{% endif %}</h2>
        <p>アカウントをまだお持ちでなければ、こちらから<a href="{% url 'account_signup' %}"><span class="accounts-link">ユーザー登録</span></a>してください。</p>
    </div>

    {% if token_fail %}
        {% url 'account_reset_password' as passwd_reset_url %}
    {% else %}
        {% if form %}
            <div class="wrapper-accounts-form">
                <form method="post" action="{{ passwd_reset_url }}" class="form-accounts">
                    {% csrf_token %}

                    <div class="form-content">
                        <input type="password1" name="password1" placeholder="新しいパスワード" autocomplete="new-password" required id="id_password1">
                    </div>
                    <div class="form-content">
                        <input type="password2" name="password2" placeholder="新しいパスワード(再入力)" autocomplete="new-password" required id="id_password2">
                    </div>
                    <button class="accounts-btn" type="submit">変更</button>
                </form>
            </div>
        {% else %}
            <div class="text-align-center">
                <p class="notion">パスワードは変更されています。</p>
            </div>
        {% endif %}
    {% endif %}
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}
パスワードリセット(確定)
{% extends 'base.html' %}

{% load static %}

{% block title %}パスワードリセット変更完了{% endblock title %}

{% block head %}<link type="text/css" rel="stylesheet" href="{% static 'css/accounts-style.css' %}">{% endblock head %}

{% block contents %}
<section class="section-accounts" id="section-signup">
    <div class="wrapperHeader">
        <h2>パスワードリセット完了</h2>
        <p>パスワードが変更されました。</p>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}

ついでに静的ファイルを設置するディレクトリ(「static/css/」)に新規ファイル accounts-style.css を作成し、下記のように編集しましょう。

accounts-style.css
/* 共通 */

:root {
    --dark-color: #442f1e;
    --semi-dark-color: #614039;
    --base-color: #d3c2bb;
    --accent-color: #598493;
    --footer-height: 100px;
}

* {
    margin: 0;
    padding: 0;
    list-style: none;
    font-family: -apple-system, Calibri、Candara、Segoe、Segoe UI、Optima、Arial、sans-serif;
    box-sizing: border-box;
}

a:link, a:visited, a:hover, a:active {
    color: inherit;
    text-decoration: none;
}

.text-align-center {
    text-align: center;
}

.notion {
    display: block;
    margin: 20px;
}

.notion a {
    border-bottom: solid 1px var(--dark-color);
}

.section-accounts {
    background-color: var(--base-color);
    min-height: calc(100vh - var(--footer-height));
}

.wrapperHeader {
    text-align: center;
    padding: 3rem;
}

.wrapperHeader p {
    display: block;
    margin-top: 2rem;
}

.accounts-link {
    font-weight: bold;
}

.form-accounts {
    width: fit-content;
    margin: 0 auto;
}

.form-content {
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: right;
    align-items: center;
    margin: 20px auto;
}

.form-content input {
    background-color: inherit;
    width: 450px;
    height: 24px;
    font-size: 16px;
    border: none;
    border-bottom: solid var(--dark-color) 1px;
    margin-left: 10px;
}

.form-content input:focus {
    outline: none;
    border-bottom: solid var(--accent-color) 2px;
}

.form-content label:after {
    position: relative;
    content: ' : ';
}

.accounts-btn {
    background-color: var(--accent-color);
    display: block;
    padding: 10px 70px;
    border: solid var(--semi-dark-color) 1px;
    font-size: 20px;
    margin: 3rem auto 0 auto;
    box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
    transition-duration: .2s;
}

.accounts-btn:hover {
    transform: translateY(-2px);
    opacity: .9;
    cursor: pointer;
}

.radio-btn {
    width: fit-content;
    margin: 0 auto;
}

django-allauthのメールの内容を変更する

サインアップやパスワードのリセット時などに送信されるメールの内容を修正します。

「accounts/templates/account/email/」ディレクトリを作成し、下記の4つのファイルを新規作成しましょう!さらに下記の内容をそれぞれコピペしてください(文言は変えていただいて構いません)。

email_confirmation_message.txt
ご登録ありがとうございます。
登録を続けるには、以下のリンクをクリックしてください。
{{ activate_url }}

サインアップ時のメールアドレス確認メールの本文です。

email_confirmation_subject.txt
ユーザー登録確認メール

サインアップ時のメールアドレス確認メールの件名です。

password_reset_key_message.txt
パスワードリセットが申請されました。処理を続けるには、以下のリンクをクリックしてください。

{{ password_reset_url }}

パスワードリセット時のメールの本文です。

password_reset_key_subject.txt
パスワードリセット

パスワードリセット時のメールの件名です。

認証ページへのリンクを貼る

サインアップ / ログインなど、認証ページへのリンクをトップページに貼り付けましょう。cafe_app内の index.html を下記のように編集します。

{% block contents %}
<div class="topPage">
    <img class="topMainImg" src="{% static 'assets/img/first-view.jpg' %}" alt="first-view">
    <div class="wrapperNav">
        <nav>
            <ul>
                <li><a href="#">HOME</a></li>
                <li><a href="{% url 'cafe_app:menu' %}">MENU</a></li>
                {% if user.is_authenticated %}
                    <li><a href="{% url 'account_logout' %}">LOG OUT</a></li>
                {% else %}
                    <li><a href="{% url 'account_signup' %}">SIGN UP</a></li>
                    <li><a href="{% url 'account_login' %}">LOG IN</a></li>
                {% endif %}
                <li><a href="{% url 'cafe_app:contact' %}">CONTACT</a></li>
            </ul>
        </nav>
    </div>
</div>
<section class="welcome">
    <div>
        <h2>ようこそ、hoge cafeへ</h2>
        <p>こちらはhoge cafeの公式サイトです。</p>
    </div>
</section>
{% endblock contents %}

{% if user.is_authenticated %} によって、ユーザーがログインしているか否かで処理を分けるています。

{% if user.is_authenticated %}
... ログインしている場合の内容 ...
{% else %}
... ログインしていない場合の内容 ...
{% endif %}

ログインしている場合はログアウトのみ、ログインしていない場合はログイン / サインアップを表示させています。

ログインしているユーザー名を表示させる

ログイン後にテンプレート側でユーザー名を取得し、表示させましょう。

なくてもいいのですが、Webアプリぽい感じをだしましょう。

index.htmlに一部追加します。

{% load account %}

<!-- ... 略 ... -->

{% block contents %}

<!-- ... 略 ... -->

<section class="welcome">
    <div>
        <h2>ようこそ、hoge cafeへ</h2>
        <p>こちらはhoge cafeの公式サイトです。</p>
        <p>{% user_display user %}</p>
    </div>
</section>
{% endblock contents %}

{% load account %} と記述してから {% user_display user %} とすることでログインしているユーザー名を表示することができます。

マイグレーション

ターミナルで下記のコマンドによりマイグレーションを行います。

(venv_cafe)$ python manage.py makemigrations
(venv_cafe)$ python manage.py migrate

マイグレーションについてはこちらで解説しております。

マイグレーションが完了すれば、無事認証機能の実装が完了です!お疲れ様でした!

この時点でのコード全体をGithubに載せております。確認用としてお使いいただければと思います。

まとめ

本セクションでは、django-allauthを使用してDjangoアプリケーションに認証機能を実装する方法をご紹介させていただきました。

次回からは、本記事で実装した認証機能を活かして、ログインしたユーザーのみが利用できる会員限定機能についてご紹介していきます。

ご覧いただきありがとうございました!

案件、ありますか?

「メインの仕事があるけれど、週1、2日だけできる仕事ないかな、、、」

「ある程度スキルが身に付いてはきたけど、そのスキルを活用できる場が欲しい」

なんて悩みが以前はありました。

自分で仕事を探しに行くのも大事ですが、蛇の道は蛇。その道の人に頼むことで、自分だけでは見つからないような案件に携わることができます。

IT PRO パートナーズでは、簡単に無料でアカウントを登録でき、さらにはエージェントさんに希望の働き方・案件の種類を提示することでお仕事を紹介してくれます!

登録自体も非常に簡単で、「エージェントさんとの面談を希望する」という欄にチェックをするだけで、エージェントさんから直接連絡をいただくことができます。

驚くほど簡単で正直拍子抜けしてしまいました笑

もしお仕事探しに困っておりましたら、一度登録し案件を眺めてみることをおすすめします!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

勤めていた設計会社を退社し、フリーランスとして活動しています
また、趣味で主にpyhonを用いて機械学習を行なっています!
現在競艇の予測モデルの開発中です。

コメント

コメント一覧 (4件)

コメントする

目次