2
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?

More than 3 years have passed since last update.

クライアントシークレットなしのCognitoユーザプールをDjangoで使う方法

Last updated at Posted at 2021-08-05

はじめに

Djangoの認証にCognitoを使う場合、いくつかライブラリが提供されているが、Django3.x非対応だったり、DRFベースだったりしてフロントエンドで使うためのものとしては選択肢が少ない。

django-cognito-reduxは上記案件に対応している貴重なライブラリである。
ただし、ユーザープールのアプリクライアントでシークレットキーを設定することが前提条件となっているため、他のウェブアプリで共有したい場合ネックとなる。

今回、関数のオーバライドによりクライアントキーなしでも利用できるようにしてみた。

対象読者

  • 認証基盤にAWS Cognitoを使っている方
  • DjangoおよびVue.jsなどJavascript系のウェブフレームワークのフロントエンド開発者

環境

  • Python-3.9.6
  • Django-3.2.5
  • django-cognito-redux-1.4.5

セットアップ

GithubのレポジトリにソースがあるのでREADME.mdに従って設定する。

動作確認

Djangoのアプリケーションを起動しブラウザでhttp://localhost:8000/にアクセスする。

FireShot Capture 051 - The install worked successfully! Congratulations! - localhost.png

特に問題がなければ管理画面http://localhost:8000/admin/にアクセスする。

FireShot Capture 052 - ログイン - Django サイト管理 - localhost.png

Cognitoユーザプールの既存アカウントでログインしてみる。

FireShot Capture 053 - サイト管理 - Django サイト管理 - localhost.png

ログインに成功すると管理画面のダッシュボードが表示される。
このサンプルではCognitoユーザにパーミッションを与えていないので特に何も表示されないがログインは成功した。

この後、ログアウトして管理者(create superuserで作成したユーザ)で再度ログインする。

FireShot Capture 054 - 変更する ユーザー を選択 - Django サイト管理 - localhost.png

すると管理者以外にCognitoのユーザが追加されている。
emailfirstnameおよびlastnameはCognitoのユーザ情報から取得して登録している。

解説

今回のサンプルは認証のみ行っている。
スクラッチから作る場合はまず手動でプロジェクト配下にaccountディレクトリを以下の構成で作る。

account/
├── __init__.py
├── backends.py
└── helpers.py

backends.pyに認証バックエンドを定義する。

class AwsCognitoAuthentication:

    def authenticate(self, request, username=None, password=None):
        if username is None:
            user, _, _ = m_helpers.process_request(request)
            return user
        else:
            try:
                result = initiate_auth(
                    {
                        'username': username,
                        'password': password,
                        'auth_flow': 'USER_PASSWORD_AUTH'
                    }
                )
        # ...

initiate_auth関数はクライアントシークレットの有無で使い分けたいので、冒頭で振り分けている。

if getattr(settings, 'APP_SECRET_KEY'):
    # クライアントシークレットがあるとき
    from django_cognito.authentication.cognito.helpers import initiate_auth
else:
    # ないとき
    from .helpers import initiate_auth

helpers.pyはオリジナルを改変して以下のようにする。

def initiate_auth(data, param_mapping=None):
    if ('username' in data and 'password' in data) \
            or ('username' in param_mapping and 'password' in param_mapping):
        auth_flow = constants.USER_PASSWORD_FLOW
        username = parse_parameter(data, param_mapping, 'username')
        password = parse_parameter(data, param_mapping, 'password')

        return initiate_auth_without_secret(username, auth_flow, password)

    else:
        raise ValueError('Unsupported auth flow')


def initiate_auth_without_secret(username, auth_flow, password=None,
                                 refresh_token=None, srp_a=None):
    auth_parameters = {}
    # ...

上記ができたらsettings.pyに設定を加える

# ...
INSTALLED_APPS = [
    'django.contrib.admin',
    # ...
    'account',
]

# ...

# backend
AUTHENTICATION_BACKENDS = [
  'django.contrib.auth.backends.ModelBackend',
  'account.backends.AwsCognitoAuthentication',
]
# ...

まとめ

今回のキモとなっているのはオリジナルのactions.pyの以下の行

def initiate_auth(username, auth_flow, password=None, refresh_token=None, srp_a=None):
    auth_parameters = {'SECRET_HASH': utils.get_cognito_secret_hash(username)}

ここでCognitoに渡すパラメータとしてSECRET_HASHを必須としている。なので

def initiate_auth(username, auth_flow, password=None, refresh_token=None, srp_a=None):
    auth_parameters = {}

とした。もちろんこのままではクライアントシークレットがある場合は認証できないので呼び出す関数を使い分けている。

2
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
2
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?