Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

PythonでAWS Cognito認証

More than 3 years have passed since last update.

急に自分内Djangoブームが到来したので、Djangoに乗り換えはじめました。で、先日Node.jsで作ったAWSのCognito認証をPythonで作り直したので晒してみます。

事前準備

PythonでAWSにアクセスするにはIAMユーザーが必要となります。ロールにAmazonCognitoPowerUserを設定しておいてください。認証だけならロールをAmazonCognitoReadOnlyとかに落としても動くと思います。
image.png

アクセスキーも必要となりますので、認証情報タブを押してアクセスキーを作っておいてください。

つづいてAWSにPythonでアクセスするためのライブラリであるboto3を入れます

pip install boto3

AWS Cognitoで認証する

.awsに設定をしていなくても、boto3を使う時にregion等の設定を行うことができます。アプリの認証用ユーザーは固定すると思いますので、.awsを使うよりboto3を使う時に設定するほうが現実的だろうと思います。

AWS_ACCESS_KEY_IDやAWS_SECRET_ACCESS_KEY等は直接プログラムに埋め込むのは危険です(特にVCSを使ってる場合は注意)。外部ファイルに記述して読み込んで使ってください。

import boto3

def cognito_auth(user, passwd):
    # 認証開始
    try:
        aws_client = boto3.client('cognito-idp',
            region_name = "ap-northeast-1",
            aws_access_key_id = "ここにAWS_ACCESS_KEY_IDを設定します",
            aws_secret_access_key = "ここにAWS_SECRET_ACCESS_KEYを設定します",
        )

        aws_result = aws_client.admin_initiate_auth(
            UserPoolId = "ここにユーザープールIDを設定します",
            ClientId = "ここにアプリクライアントIDを設定します",
            AuthFlow = "ADMIN_NO_SRP_AUTH",
            AuthParameters = {
                "USERNAME": user,
                "PASSWORD": passwd,
            }
        )

        # 認証完了
        return aws_result

    except :
        # 認証失敗
        return None

認証が成功すれば

aws_result["AuthenticationResult"]["AccessToken"]
aws_result["AuthenticationResult"]["RefreshToken"]
aws_result["AuthenticationResult"]["IdToken"]

のようにしてトークンを拾えます。

トークンが有効かチェック

トークンはjwt形式なのでjwtライブラリで分解して……ってやろうとしたら、VC Build Tool入れて、pycrypto入れて、pyjwt入れて、winrandomの参照先変えて、decode命令呼んだら、RS256デコードしようとしてエラー吐いて死んだ/(^o^)\

どうやってもWindowsだと暗号解除部分でエラー出すので、自力で確認するの諦めて、AWSにおまかせする方向にしました。

というわけで、保有しているトークンを使ってAWSからユーザー情報を取得します。トークンが無効ならAWSがエラーを出してくれるはずです。期限切れの確認はやってないので、もしかしたら間違ってるかもしれません。

認証成功時に

# 認証成功
req.session["aws_cognito"] = {
    "user": "認証したユーザーの名前",
    "access_token": aws_result["AuthenticationResult"]["AccessToken"],
    "refresh_token": aws_result["AuthenticationResult"]["RefreshToken"]
}

のようにして、req.sessionにトークンを保存した前提でのプログラムとなります。

import boto3

def cognito_auth_check(req):
    # 認証開始
    # *********************************************************************
    try:
        # AWSクライアント認証
        aws_client = boto3.client('cognito-idp',
            region_name = "ap-northeast-1",
            aws_access_key_id = "ここにAWS_ACCESS_KEY_IDを設定します",
            aws_secret_access_key = "ここにAWS_SECRET_ACCESS_KEYを設定します",
        )

        try:
            # 認証済みトークンの有効性確認
            return aws_client.get_user(AccessToken=req.session["aws_cognito"]["access_token"])

        except:
            # 失敗。期限切れかもしれないのでREFRESH_TOKENで再認証
            try:
                aws_result = aws_client.admin_initiate_auth(
                    UserPoolId = "ここにユーザープールIDを設定します",
                    ClientId = "ここにアプリクライアントIDを設定します",
                    AuthFlow = "REFRESH_TOKEN_AUTH",
                    AuthParameters = {
                        "USERNAME": req.session["aws_cognito"]["user"],
                        "REFRESH_TOKEN": req.session["aws_cognito"]["refresh_token"]
                    }
                )
                # アクセストークン更新
                req.session["aws_cognito"]["access_token"] = aws_result["AuthenticationResult"]["AccessToken"]

                # 再度認証確認してみる
                return aws_client.get_user(AccessToken=req.session["aws_cognito"]["access_token"])

            except:
                # REFRESH_TOKENすら期限切れや、接続障害によって失敗する可能性
                return None

    except:
        # AWSへの接続失敗。IAM変えた? AWS接続障害の可能性も
        return None

成功すればユーザー情報が取得できます。

Djangoでデコレーターとして使う

デコレーターを作っておくとDjangoのlogin_requiredデコレーターのように

@login_required
def api(req):
    return HttpResponse('認証済みページの表示')

と、関数の前にポンッとデコレーターを置いたらページを認証が必要な状態にできるのが便利そうです。なのでデコレーターも作ってみました。

Djangoでのプログラムです。

from django.http import HttpResponse

# AWS Cognitoで認証済みであることを要求するデコレーター
def cognito_required(func):
    def wrap(req, *args, **kwargs):
        # 認証確認
        req.user = cognito_auth_check(req)
        if req.user != None:
            # デコレータ下の関数を実行
            return func(req, *args, **kwargs)

        # 失敗終了
        return HttpResponse('認証が必要です', status=401)

    wrap.__doc__=func.__doc__
    wrap.__name__=func.__name__
    return wrap

これを、account/aws_auth.pyとかで作っておけば、

from account.aws_auth import cognito_required

@cognito_required
def api(req):
    return HttpResponse('認証済みページの表示')

と、簡単に認証が必要なapiにすることができてステキ♡

jp_ibis
_(:3」∠)_
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away