DRFでセッション認証

# 概要

PythonのDjango REST Framweorkで、セッション認証を実装する方法を手順を交えて紹介します。

# セッション認証とは?

セッション認証とは、Webアプリケーションにおいて、ユーザーがログインした状態を管理する認証の一つです。セッション認証では、ユーザーがログインすると、サーバー側でセッションを作成し、そのセッションにログイン情報を保存します。その後、セッションの有効期限が切れるまで、ユーザーがリクエストを送信するたびに、そのリクエストに含まれるセッションIDを使用して、サーバー側でセッションオブジェクトを特定し、ユーザーがログイン済みかどうかを確認します。

# Django REST Frameworkでのセッション認証の方法

## 各種バージョン

Python: 3.10
Django: 4.1.4
Docker: 20.10.11
docker-compose: 2.2.1

## 手順

### Djangoの基本環境構築

Qiitaの[こちらの記事](https://qiita.com/ryo-keima/items/f9766572cd4843747e24#%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89)の「仮想環境構築」および、「Djangoプロジェクト作成」欄を参照ください。

今回、プロジェクト名は`session_auth`とします。

### セッション認証の実装手順

ユーザー情報を管理するアプリを作成します。

```text
(venv) session_auth % python manage.py startapp account
```

カスタムユーザーを作成します。

```python:session_auth/account/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, username, password, **extra_fields):
        email = self.normalize_email(email)
        user = self.model(email=email, username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        user.full_clean()

        return user

    def create_user(self, email, username, 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,
            username=username,
            password=password,
            **extra_fields,
        )

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

class User(AbstractBaseUser, PermissionsMixin):

    username = models.CharField(
        verbose_name=_("username"),
        unique=True,
        max_length=150
    )
    email = models.EmailField(
        verbose_name=_("Email Address"),
        unique=True
    )
    age = models.IntegerField(
        verbose_name=_("age"),
        null=True,
        blank=True
    )
    is_superuser = models.BooleanField(
        verbose_name=_("is_superuer"),
        default=False
    )
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
    )

    objects = UserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    def __str__(self):
        return self.username
```

settings.pyを編集します。

```python:session_auth/project/settings.py
...

ALLOWED_HOSTS = ["*"] # ワイルドカードを追加

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # djangorestframeworkを追加
    'account' # 作成したaccountアプリを追加
]

...

AUTH_USER_MODEL = "account.User" # accountアプリのUserモデルをデフォルトで使用する認証ユーザーモデルとして設定する

...

SESSION_ENGINE = 'django.contrib.sessions.backends.db' # セッションを何で保存するかを指定する。この場合はDBで管理する。
```

セッションを管理画面で確認できるよう、admin.pyを編集します。

```python:session_auth/account/admin.py
from django.contrib import admin
from django.contrib.sessions.models import Session

admin.site.register(Session)
```

ここまでできましたら、マイグレートします。

```
(venv) session_auth % python manage.py makemigrations
(venv) session_auth % python manage.py migrate
```

ここで、スーパーユーザーを作成し、管理画面にログインできることを確認します。

```
(venv) session_auth % python manage.py createsuperuser
Username (leave blank to use 'testuser'): testuser
Email address: test@example.com
Password: 
Password (again):
Superuser created successfully.
```

開発用サーバーを起動します。

```
(venv) session_auth % python manage.py runserver
```

ブラウザで[http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/)を開き、管理画面にアクセスします。

![Screen Shot 2023-02-21 at 20.34.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/f9aea770-d9f0-85cf-dbb5-7189980a1064.png)

先ほど作成した、スーパーユーザーアカウント情報で、ログインします。

![Screen Shot 2023-02-21 at 20.34.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/39154ac7-bf06-9513-f897-f65fb624b958.png)

Sessionsを確認すると、1つセッションが作成されていることを確認できます。

![Screen Shot 2023-02-21 at 20.35.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/28274df1-e5f8-4e00-a98b-9eabe8fd2c64.png)

確認ができましたら、一度ログアウトしておきましょう。

続いて、serializers.pyを作成します。

```python:session_auth/account/serializers.py
from rest_framework import serializers
from .models import User


class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = "__all__"
```

views.pyを編集します。

```python:session_auth/account/views.py
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated

from .models import User
from .serializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    authentication_classes = (SessionAuthentication,)
    permission_classes = (IsAuthenticated, )
```

UserViewSetのauthentication_classesにSessionAuthenticationを、
permission_classesにIsAuthenticatedを設定します。
これで、UserViewsSetへアクセスするには、セッション認証が必須となります。
ちなみに、viewsets.ModelViewSetとは、一覧取得、詳細取得、新規作成、更新、削除のViewをまとめて管理してくれる非常に便利なビューです。
(今回は認証の確認のため一覧取得の機能しか使いません。)
UserViewSetへアクセスできるようにするため、urls.pyを作成し、project/urls.pyを更新します。

```python:session_auth/account/urls.py
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import UserViewSet

router = DefaultRouter()
router.register(r'account', UserViewSet)

urlpatterns = [
    path('', include(router.urls))
]
```

```python:session_auth/project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('account.urls')), # account/urls.pyを指定
]
```

これで、/api/v1/accountにアクセスすると、セッション認証が実行され、認証が通ると、ユーザー情報を取得できるようになっているはずです！

### 動作確認

確認してみましょう！

まずは、[http://127.0.0.1:8000/api/v1/account](http://127.0.0.1:8000/api/v1/account)にアクセスしてみましょう。

以下のように、エラーがでたら正しくセッション認証が設定されています。
![session-auth-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/83840666-ed5c-7ee2-bf77-fa5d19154bdb.png)

続いて、セッションを作成するため、管理画面にログインしてみましょう。
Sessionsを確認すると、セッションが作成されていますね。
![session-auth-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/b75eef5c-53e1-ad92-d130-ac5d4b3e2de0.png)

この状態で再度[http://127.0.0.1:8000/api/v1/account](http://127.0.0.1:8000/api/v1/account)にアクセスしてみます。
![session-auth-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/f7749d00-4b9c-2ded-4edf-93281674c521.png)



ユーザー情報が取得できました！😊

以上で、DRFのセッション認証の実装方法の解説を終わります。

ありがとうございました！