Django-allauth

認証DjangoライブラリPythonWeb開発ソーシャル認証MFAOAuth

認証ライブラリ

Django-allauth

概要

Django-allauthは、Djangoアプリケーション向けの包括的な認証・認可ライブラリです。従来のユーザー名/パスワード認証に加えて、50以上のソーシャルプロバイダーを使用したソーシャル認証、多要素認証(MFA)、パスキー認証などの最新の認証方式をサポートしています。2025年現在、Django REST FrameworkとのネイティブAPIサポートが追加され、ヘッドレス認証やSPA(Single Page Application)との統合が大幅に改善されています。

詳細

主要機能

  • 統合認証システム: アカウント登録、ログイン、パスワード管理、メール認証を統一インターフェースで提供
  • ソーシャル認証: Google、GitHub、Facebook、Twitter、Appleなど50以上のプロバイダーに対応
  • 多要素認証(MFA): TOTP、WebAuthn(パスキー)、リカバリコードをサポート
  • ヘッドレス対応: 2025年に追加されたREST API機能により、React、Vue.jsなどのSPAとの統合が容易
  • カスタマイズ性: フォーム、テンプレート、アダプター、バリデーターの完全なカスタマイズが可能
  • セキュリティ: CSRF保護、レート制限、セッション管理、セキュアなトークン処理を内蔵

アーキテクチャ

Django-allauthは複数のDjangoアプリで構成されています:

  • allauth.account: 基本的なアカウント管理機能
  • allauth.socialaccount: ソーシャル認証機能
  • allauth.mfa: 多要素認証機能
  • allauth.headless: REST API(2025年新機能)

2025年の新機能

  • Django REST Framework統合: ネイティブなAPI認証サポート
  • React SPA例: Django Ninjaとの統合サンプル
  • ヘッドレスセッショントークン: SPAでのトークンベース認証
  • 改善されたパスキーサポート: WebAuthn実装の強化

メリット・デメリット

メリット

  • オールインワンソリューション: 認証に関するほぼ全ての機能を単一ライブラリで提供
  • 豊富なソーシャルプロバイダー: 50以上のプロバイダーに対応、設定も簡単
  • セキュリティベストプラクティス: Django開発者が推奨するセキュリティ機能を内蔵
  • 高いカスタマイズ性: フォーム、テンプレート、ビジネスロジックを細かくカスタマイズ可能
  • アクティブな開発: 継続的なアップデートと新機能追加
  • 包括的なドキュメント: 詳細なドキュメントと豊富なサンプルコード
  • Django REST Framework対応: 2025年にネイティブAPI対応を追加
  • MFA完全対応: TOTP、WebAuthn、リカバリコードによる最新のセキュリティ

デメリット

  • 学習コストがやや高い: 豊富な機能により初期設定が複雑
  • Django専用: 他のPythonフレームワークでは使用不可
  • 依存関係: 大規模なライブラリのため、プロジェクトサイズが増加
  • マイグレーション: 既存のDjangoプロジェクトへの統合時、既存の認証システムとの競合が発生する可能性

参考ページ

書き方の例

1. インストールとプロジェクト設定

# ソーシャル認証を含む完全版のインストール
pip install "django-allauth[socialaccount]"

# MFA機能も含める場合
pip install "django-allauth[socialaccount,mfa]"
# settings.py
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    
    # Django-allauth必須アプリ
    'allauth',
    'allauth.account',
    
    # ソーシャル認証(オプション)
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    'allauth.socialaccount.providers.github',
    'allauth.socialaccount.providers.facebook',
    
    # MFA(オプション)
    'allauth.mfa',
    
    # ヘッドレス対応(2025年新機能)
    'allauth.headless',
    
    'your_app',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    # Django-allauth必須ミドルウェア
    'allauth.account.middleware.AccountMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# サイトフレームワーク設定
SITE_ID = 1

# テンプレート設定
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',  # allauth必須
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# 基本認証設定
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = False

# ログイン・ログアウト後のリダイレクト
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'

# URLs設定
# urls.py
from django.contrib import admin
from django.urls import path, include

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

2. ソーシャルログイン設定(Google、GitHub等)

# settings.py
SOCIALACCOUNT_PROVIDERS = {
    'google': {
        'SCOPE': [
            'profile',
            'email',
        ],
        'AUTH_PARAMS': {
            'access_type': 'online',
        },
        'VERIFIED_EMAIL': True,
        'VERSION': 'v2',
        'APP': {
            'client_id': 'YOUR_GOOGLE_CLIENT_ID',
            'secret': 'YOUR_GOOGLE_CLIENT_SECRET',
            'key': ''
        }
    },
    'github': {
        'SCOPE': [
            'user:email',
        ],
        'VERIFIED_EMAIL': True,
        'APP': {
            'client_id': 'YOUR_GITHUB_CLIENT_ID',
            'secret': 'YOUR_GITHUB_CLIENT_SECRET',
            'key': ''
        }
    },
    'facebook': {
        'METHOD': 'oauth2',
        'SCOPE': ['email', 'public_profile'],
        'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
        'INIT_PARAMS': {'cookie': True},
        'FIELDS': [
            'id',
            'first_name',
            'last_name',
            'name',
            'email',
        ],
        'EXCHANGE_TOKEN': True,
        'VERIFIED_EMAIL': False,
        'VERSION': 'v13.0',
    }
}

# ソーシャル認証の追加設定
SOCIALACCOUNT_LOGIN_ON_GET = False  # セキュリティのためPOSTを要求
SOCIALACCOUNT_EMAIL_AUTHENTICATION = True  # 信頼できるプロバイダーのみ
SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_QUERY_EMAIL = True
<!-- templates/socialaccount/login.html -->
{% load socialaccount %}

<div class="social-login">
    <h3>ソーシャルアカウントでログイン</h3>
    {% get_providers as socialaccount_providers %}
    {% for provider in socialaccount_providers %}
        <div class="provider-login">
            <a href="{% provider_login_url provider.id %}" 
               class="btn btn-{{ provider.id }}">
                {{ provider.name }}でログイン
            </a>
        </div>
    {% endfor %}
</div>

3. ユーザー登録とメール認証

# 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 = '[email protected]'
EMAIL_HOST_PASSWORD = 'your-app-password'

# アカウント設定
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 3
ACCOUNT_EMAIL_SUBJECT_PREFIX = '[My Site] '
ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https'

# 登録フィールドのカスタマイズ
ACCOUNT_SIGNUP_FIELDS = ['first_name', 'last_name']
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False

# パスワード強度設定
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 8,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
# forms.py - カスタム登録フォーム
from allauth.account.forms import SignupForm
from django import forms

class CustomSignupForm(SignupForm):
    first_name = forms.CharField(max_length=30, label='名')
    last_name = forms.CharField(max_length=30, label='姓')
    phone_number = forms.CharField(max_length=15, label='電話番号', required=False)

    def save(self, request):
        user = super().save(request)
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.save()
        
        # プロフィール情報の保存
        if hasattr(user, 'profile'):
            user.profile.phone_number = self.cleaned_data['phone_number']
            user.profile.save()
        
        return user

# settings.py
ACCOUNT_FORMS = {'signup': 'your_app.forms.CustomSignupForm'}

4. カスタマイズとテンプレート設定

<!-- templates/account/login.html -->
{% extends "account/base.html" %}
{% load i18n %}
{% load allauth %}

{% block head_title %}{% trans "Sign In" %}{% endblock %}

{% block content %}
<div class="login-container">
    <div class="login-form">
        <h1>ログイン</h1>
        
        {% url 'account_login' as login_url %}
        {% element form method="post" action=login_url %}
            {% slot body %}
                {% csrf_token %}
                {% element fields form=form %}
                {% endelement %}
                {% if redirect_field_value %}
                    <input type="hidden" name="{{ redirect_field_name }}" 
                           value="{{ redirect_field_value }}" />
                {% endif %}
            {% endslot %}
            {% slot actions %}
                <button class="btn btn-primary" type="submit">
                    ログイン
                </button>
                <a class="btn btn-link" href="{% url 'account_reset_password' %}">
                    パスワードを忘れた方
                </a>
            {% endslot %}
        {% endelement %}
        
        <!-- ソーシャルログイン -->
        {% include "socialaccount/snippets/login.html" with process="login" %}
        
        <div class="signup-link">
            <p>アカウントをお持ちでない方は 
               <a href="{% url 'account_signup' %}">新規登録</a>
            </p>
        </div>
    </div>
</div>
{% endblock %}
# adapters.py - カスタムアダプター
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.contrib.auth import get_user_model

User = get_user_model()

class CustomAccountAdapter(DefaultAccountAdapter):
    def is_open_for_signup(self, request):
        return True  # 登録受付状況を制御
    
    def get_login_redirect_url(self, request):
        # ログイン後のリダイレクト先をカスタマイズ
        if request.user.is_superuser:
            return '/admin/'
        return '/'
    
    def send_mail(self, template_prefix, email, context):
        # メール送信ロジックをカスタマイズ
        context['site_name'] = 'My Awesome Site'
        return super().send_mail(template_prefix, email, context)

class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    def pre_social_login(self, request, sociallogin):
        # ソーシャルログイン前の処理
        pass
    
    def populate_user(self, request, sociallogin, data):
        # ソーシャルアカウントからユーザー情報を取得
        user = super().populate_user(request, sociallogin, data)
        user.is_active = True
        return user

# settings.py
ACCOUNT_ADAPTER = 'your_app.adapters.CustomAccountAdapter'
SOCIALACCOUNT_ADAPTER = 'your_app.adapters.CustomSocialAccountAdapter'

5. API認証とDjango REST Framework統合

# settings.py - 2025年新機能
INSTALLED_APPS = [
    # ... 他のアプリ
    'rest_framework',
    'allauth.headless',  # ヘッドレス対応
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'allauth.headless.authentication.HeadlessSessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# ヘッドレス設定(SPA対応)
HEADLESS_FRONTEND_URLS = {
    "account_confirm_email": "https://yourapp.com/account/verify-email/{key}",
    "account_reset_password": "https://yourapp.com/account/password/reset",
    "account_reset_password_from_key": "https://yourapp.com/account/password/reset/key/{key}",
    "account_signup": "https://yourapp.com/account/signup",
    "socialaccount_login_error": "https://yourapp.com/account/provider/callback"
}

# API URL設定
# urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('allauth.urls')),
    path('api/auth/', include('allauth.headless.urls')),  # API認証エンドポイント
    path('api/', include('your_app.api.urls')),
]
# api/views.py - REST APIビュー
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from allauth.headless.constants import SESSION_TOKEN_KEY

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_profile(request):
    """ユーザープロフィール取得API"""
    user = request.user
    return Response({
        'user_id': user.id,
        'email': user.email,
        'first_name': user.first_name,
        'last_name': user.last_name,
        'is_authenticated': True,
    })

@api_view(['POST'])
def api_login(request):
    """API認証専用ログイン"""
    from allauth.headless.account.views import LoginView
    view = LoginView()
    response = view.post(request)
    
    if response.status_code == 200:
        # セッショントークンをレスポンスに含める
        token = request.session.get(SESSION_TOKEN_KEY)
        response.data['session_token'] = token
    
    return response
// React/Vue.jsでの使用例
// api-client.js
class AuthAPI {
    constructor() {
        this.baseURL = 'http://localhost:8000/api/auth/';
        this.token = localStorage.getItem('sessionToken');
    }

    async login(email, password) {
        const response = await fetch(`${this.baseURL}login/`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': this.getCSRFToken(),
            },
            body: JSON.stringify({ email, password })
        });
        
        const data = await response.json();
        if (data.session_token) {
            localStorage.setItem('sessionToken', data.session_token);
            this.token = data.session_token;
        }
        return data;
    }

    async getProfile() {
        const response = await fetch('/api/profile/', {
            headers: {
                'Authorization': `Bearer ${this.token}`,
                'X-CSRFToken': this.getCSRFToken(),
            }
        });
        return response.json();
    }

    getCSRFToken() {
        return document.querySelector('[name=csrfmiddlewaretoken]')?.value;
    }
}

6. テストと高度な設定

# tests.py
from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse
from allauth.account.models import EmailAddress
from allauth.socialaccount.models import SocialAccount

User = get_user_model()

class AuthenticationTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.user_data = {
            'email': '[email protected]',
            'password1': 'testpass123',
            'password2': 'testpass123',
        }

    def test_user_signup(self):
        """ユーザー登録テスト"""
        response = self.client.post(
            reverse('account_signup'),
            self.user_data
        )
        self.assertEqual(response.status_code, 302)
        
        # ユーザーが作成されたことを確認
        user = User.objects.get(email=self.user_data['email'])
        self.assertFalse(user.is_active)  # メール認証前
        
        # メール認証オブジェクトが作成されたことを確認
        email_address = EmailAddress.objects.get(user=user)
        self.assertFalse(email_address.verified)

    def test_email_verification(self):
        """メール認証テスト"""
        user = User.objects.create_user(
            email='[email protected]',
            password='testpass123'
        )
        email_address = EmailAddress.objects.create(
            user=user,
            email=user.email,
            verified=False,
            primary=True
        )
        
        # 認証キーを生成してテスト
        key = email_address.send_confirmation()
        response = self.client.post(
            reverse('account_confirm_email', kwargs={'key': key})
        )
        
        email_address.refresh_from_db()
        self.assertTrue(email_address.verified)

    def test_social_login(self):
        """ソーシャルログインテスト"""
        # モックユーザーとソーシャルアカウントを作成
        user = User.objects.create_user(
            email='[email protected]',
            password='testpass123'
        )
        social_account = SocialAccount.objects.create(
            user=user,
            provider='google',
            uid='123456789',
            extra_data={'email': '[email protected]'}
        )
        
        # ログインテスト
        self.client.force_login(user)
        response = self.client.get(reverse('account_profile'))
        self.assertEqual(response.status_code, 200)

    def test_password_reset(self):
        """パスワードリセットテスト"""
        user = User.objects.create_user(
            email='[email protected]',
            password='oldpass123'
        )
        
        response = self.client.post(
            reverse('account_reset_password'),
            {'email': user.email}
        )
        self.assertEqual(response.status_code, 302)
        
        # メールが送信されることを確認
        from django.core import mail
        self.assertEqual(len(mail.outbox), 1)

    def test_api_authentication(self):
        """API認証テスト(2025年新機能)"""
        user = User.objects.create_user(
            email='[email protected]',
            password='testpass123'
        )
        
        # API経由でログイン
        response = self.client.post('/api/auth/login/', {
            'email': user.email,
            'password': 'testpass123'
        })
        self.assertEqual(response.status_code, 200)
        
        # セッショントークンが返されることを確認
        data = response.json()
        self.assertIn('session_token', data)
# settings/production.py - 本番環境設定
import os

# セキュリティ設定
ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https'
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# メール設定(SendGrid等を使用)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'apikey'
EMAIL_HOST_PASSWORD = os.environ.get('SENDGRID_API_KEY')

# レート制限とセキュリティ
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 86400  # 24時間
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1

# キャッシュ設定(Redis推奨)
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': os.environ.get('REDIS_URL'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

# ロギング設定
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': 'django_allauth.log',
        },
    },
    'loggers': {
        'allauth': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
}

このMDXファイルでは、Django-allauthライブラリについて2025年の最新機能を含む包括的な情報を提供しています。Context7から取得した詳細な設定情報、WebSearchで確認した最新のDjango REST Framework統合機能、そして実践的な6つのコード例を含んでいます。特に2025年に追加されたヘッドレス認証機能とSPA統合については詳しく解説しています。