Django、django-allauthで、Facebookログインの設定をする(WEBブラウザ＆スマフォ)

# 概要
- Djangoフレームワーク＋django-allauthパッケージで、Facebookログインを実装します

# 準備
- 環境構築 のリンクにあるように、docker compose で環境構築し、初期画面としてロケットの画像が出る状態にします
    - [Django、django-allauthの環境をdocker compseで構築する](https://qiita.com/jansnap/items/19068bf7500643174163) をご覧ください

# `settings.py` の設定

- 基本は [公式ドキュメント](https://docs.allauth.org/en/latest/installation/quickstart.html) の通りです

- allauth、facebookログインの設定をします
- `INSTALLED_APPS` に `"django.contrib.sites",` が必要ですが、公式ドキュメントには書かれていません


```
--- a/dj_fb_login/dj_fb_login/settings.py
+++ b/dj_fb_login/dj_fb_login/settings.py
@@ -37,6 +37,13 @@ INSTALLED_APPS = [
     "django.contrib.sessions",
     "django.contrib.messages",
     "django.contrib.staticfiles",
+    # sites
+    "django.contrib.sites",
+    # django-allauth
+    'allauth',
+    'allauth.account',
+    # Optional -- requires install using `django-allauth[socialaccount]`.
+    'allauth.socialaccount',
+    # ... include the providers you want to enable:
+    'allauth.socialaccount.providers.facebook',
 ]
 
 MIDDLEWARE = [
@@ -47,6 +54,8 @@ MIDDLEWARE = [
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    # Add the account middleware:
+    "allauth.account.middleware.AccountMiddleware",
 ]

 ROOT_URLCONF = "dj_fb_login.urls"
@@ -62,11 +71,20 @@ TEMPLATES = [
                 "django.template.context_processors.request",
                 "django.contrib.auth.context_processors.auth",
                 "django.contrib.messages.context_processors.messages",
+                # `allauth` needs this from django
+                'django.template.context_processors.request',
             ],
         },
     },
 ]

+AUTHENTICATION_BACKENDS = [
+    # Needed to login by username in Django admin, regardless of `allauth`
+    'django.contrib.auth.backends.ModelBackend',
+    # `allauth` specific authentication methods, such as login by email
+    'allauth.account.auth_backends.AuthenticationBackend',
+]
+
 WSGI_APPLICATION = "dj_fb_login.wsgi.application"


@@ -125,3 +143,31 @@ STATIC_URL = "static/"
 # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field

 DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+
+
+# Site Setting for Login with Facebook.
+SITE_ID = 1
+
+# Provider specific settings
+SOCIALACCOUNT_PROVIDERS = {
+    "facebook": {
+        "METHOD": "oauth2",
+        "SDK_URL": "//connect.facebook.net/{locale}/sdk.js",
+        "SCOPE": ["email", "public_profile"],
+        "AUTH_PARAMS": {"auth_type": "reauthenticate"},
+        "INIT_PARAMS": {"cookie": True},
+        "FIELDS": [
+            "id",
+            "email",
+            "name",
+            "first_name",
+            "last_name",
+            "gender",
+        ],
+        "EXCHANGE_TOKEN": True,
+        "VERIFIED_EMAIL": False,
+        "VERSION": "v7.0",
+        #
+        "LOCALE_FUNC": lambda request: "ja_JP",
+    }
+}
```


# データベースにallauthで必要なテーブルを作成

- Dockerコンテナ内で、データベースのマイグレーションを行います
```
docker exec -it django-fb-login-web-1 python manage.py migrate
```

# Facebookアプリの審査に必要な仕組みの導入

- 開発者ページの「 [App Review - Content](https://developers.facebook.com/docs/resp-plat-initiatives/app-review/content) 」によると、プライバシーポリシーのページを作る必要があります
- 同じく開発者ページの「 [Developer Data Security Best Practices](https://developers.facebook.com/docs/development/data-security) 」によると、利用者が退会したい場合の処理(アカウントの削除)ができる必要があるようです(利用者の住む国や地域によるかも知れません)


# Facebookでアプリを作成
- Facebookでアプリを作成し、`App ID` と `App secret` をメモしておきます
    - [Facebookログイン用のFacebookアプリを作成する](https://qiita.com/jansnap/items/92261ff26bd81dd66c8e) をご覧ください


# サイトURLの設定
- `https://localhost:5005/admin` を開いて、ログインして管理画面を表示します
- 「SITES」－「Sites」をクリックして、`example.com` を `localhost` に変更します
    - 実際のドメインがある場合は、読み替えてください(以下、同じ)

# データベースにFacebook上のアプリを設定
- `https://localhost:5005/admin` を開いて、ログインして管理画面を表示します
- 「SOCIAL ACCOUNTS」－「Social applications」の「Add」をクリックします
- 以下のように設定し、「Save」をクリックします
    - Provider: Facebook
    - Name: アプリ名
    - Client ID: 上記で作成した `App ID`
    - Secret key: 上記で作成した `App secret`
    - Sites: `Available sites` から上記で作成したサイト `localhost` を選択して、右向き矢印ボタンを押して `Chosen sites` に移す
        - （上記はDBの `socialaccount_socialapp` に入ります）
    - Saveボタンで保存します

- 以下のコマンドで、fixture用のyamlファイルを出力できます。ただし、アプリのID(client_id)やsecertが含まれてしまうので、ご注意ください。頻繁に再設定しない場合は、手動で上記の手順を行うのがよさそうです。
    - `python manage.py dumpdata socialaccount.SocialApp --output socialapp_data.yml --format yaml`
```
- model: socialaccount.socialapp
  pk: 1
  fields:
    provider: facebook
    provider_id: facebook
    name: Myapp01
    client_id: '1234567890'
    secret: '9999999999'
    key: ''
    settings: {}
    sites:
    - 1
```



# urlルーティングの追加
- 今回作成したプロジェクト内の `urls.py` に、allauthを使うパスを追加します
    - すでに `accounts/` が定義されている場合は、その下に追加します

```
--- a/dj_fb_login/dj_fb_login/urls.py
+++ b/dj_fb_login/dj_fb_login/urls.py
@@ -15,8 +15,9 @@ Including another URLconf
     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 """
 from django.contrib import admin
-from django.urls import path
+from django.urls import include, path

 urlpatterns = [
     path("admin/", admin.site.urls),
+    path('accounts/', include('allauth.urls')),
+    path("accounts/", include("allauth.socialaccount.urls")),
 ]
 ```

# ブラウザで表示確認
- `http://localhost:5005/accounts/login/` を開きます
    - adminで入ったままの場合は、`http://localhost:5005/accounts/logout/` を開いて、ログアウトします
- 下側にある「Or use a third-party」の「Facebook」リンクをクリックします
- 下側にある「Continue」ボタンをクリックします
- Facebookの認証画面が表示されます
    - アプリIDやApp secretが間違っていると、エラーが出ます
    - Facebookのアプリ側の設定が間違っていると「アクティブでないアプリです」「現在このアプリにアクセスできません。アプリの開発者は問題を認識しています。アプリが再びアクティブになった時点でログインできるようになります。」と表示されます


# テンプレートの編集
- 公式サイトの [templates](https://docs.allauth.org/en/latest/common/templates.html) をご覧ください

- [GitHubの該当リポジトリ](https://github.com/pennersr/django-allauth/blob/main/allauth/templates/socialaccount/login.html)



## 既存のテンプレートに「Login with Facebook」ボタンを追加する

以下を追加
```jinja
{% load socialaccount %}
```

以下を追加
```html
<div>
  <a href="{% provider_login_url 'facebook' %}" class="btn-facebook">Signup with Facebook</a>
</div>
```

以下のスタイルシートを追加して、テンプレートから読み込み
```css
.btn-facebook {
    display: inline-block;
    background-color: #1877F2;

    color: white;
    padding: 10px 20px;

    text-align: center;
    text-decoration: none;

    border-radius: 5px;
}
.btn-facebook:hover {
    background-color: #145dbf;
    color: white;
}
```

## Facebookログイン用のページを新規作成

- `templates/socialaccount/login.html` というファイルを新規作成します

- 以下のように記述します
  - 元の　[GitHubの該当リポジトリ](https://github.com/pennersr/django-allauth/blob/main/allauth/templates/socialaccount/login.html)　からコピペして、postするボタンの部分を書き換えています
      - 元は標準の灰色の小さいボタンだったため

```
{% extends "base.html" %}  <!-- この辺は既存のテンプレートと合わせてください -->
{% load i18n %}
{% load allauth %}
{% load socialaccount %}
{% load static %}
{% block head_content %}  <!-- この辺は既存のテンプレートと合わせてください -->
  <link href="{% static 'css/btn_facebook.css' %}" rel="stylesheet">
{% endblock %}

{% block head_title %}
    {% trans "Sign In" %}
{% endblock head_title %}
{% block content %}
    {% if process == "connect" %}
        {% element h1 %}
            {% blocktrans with provider.name as provider %}Connect {{ provider }}{% endblocktrans %}
        {% endelement %}
        {% element p %}
            {% blocktrans with provider.name as provider %}You are about to connect a new third-party account from {{ provider }}.{% endblocktrans %}
        {% endelement %}
    {% else %}
        {% element h1 %}
            {% blocktrans with provider.name as provider %}Sign In Via {{ provider }}{% endblocktrans %}
        {% endelement %}
        {% element p %}
            {% blocktrans with provider.name as provider %}You are about to sign in using a third-party account from {{ provider }}.{% endblocktrans %}
        {% endelement %}
    {% endif %}
    {% element form method="post" no_visible_fields=True %}
        {% slot actions %}
            {% csrf_token %}
            <button type="submit" class="btn-facebook">
                {% trans "Login with Facebook" %}
            </button>
        {% endslot %}
    {% endelement %}
{% endblock content %}
```

## スマフォでFacebookアプリを起動するように修正

- スマフォからのアクセス時にFacebookアプリを自動で起動するには、上記の`{% endblock content %}`の直前に、以下のコードを追加します
    - IDの `123456789012345` は、書き換えてください(3か所とも)
    - Android版のアプリを起動する際は、`fb://` で起動しなかったので、`intent://` を使っています

```javascript
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var userAgent = navigator.userAgent || navigator.vendor || window.opera;
            var fbAppUrl = "intent://page/123456789012345#Intent;package=com.facebook.katana;scheme=fb;end";
            var fbWebUrl = "https://www.facebook.com/123456789012345";

            function openFacebookApp(url) {
                var start = Date.now();
                var timeout = setTimeout(function() {
                    if (Date.now() - start < 2000) {
                        window.location.href = fbWebUrl;
                    }
                }, 1500);

                window.location.href = url;
                window.addEventListener('blur', function() {
                    clearTimeout(timeout);
                });
            }

            if (/android/i.test(userAgent)) {
                // Androidデバイスの場合
                openFacebookApp(fbAppUrl);
            } else if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
                // iOSデバイスの場合
                window.location.href = "fb://profile/123456789012345";
            }
        });
    </script>
```