LoginSignup
4
4

More than 3 years have passed since last update.

Boto3でAmazon CognitoのOIDC Providerを作成する

Last updated at Posted at 2019-05-30

Amazon CognitoとSlackで無理矢理OpenID Connect - QiitaでCognitoとSlackの連携環境を作ってみました。

Cognito設定が割と面倒で忘れそうなので、設定をスクリプトでできるようにしておきます。

PythonでAmazon Cognitoのユーザープールを作成してみる - Qiita で紹介されているスクリプトを元にして、今回必要な設定を追加していきました。

環境構築

環境構築の方法は変わっていませんので、参考記事 の通りでお願いします。

実装

ソース全体は以下の通りです。

多少長くなったので、使ってないコメント文は削除しました。

名称 設定値
POOL_NAME ユーザプール名称
DOMAIN_NAME Cognito* に設定するドメイン
CLIENT_ID SlackのClientID
CLIENT_SECRET SlackのClient Secret
AUTHORIZATION_ENDPOINT OIDC ProviderのAuthorization Endpoint
TOKEN_ENDPOINT OIDC ProviderのToken Endpoint
USERINFO_ENDPOINT OIDC ProviderのUserinfo Endpoint

は実際の物に合わせてください

POOL_NAMEに当てはまるUSER_POOLが存在する場合は削除するようになっているので、既にある場合は注意してください

main.py
import boto3

POOL_NAME = 'test-pool'
PROVIDER_NAME = 'Slack'
DOMAIN_NAME = 'test'
DUMMY_URL = 'https://oidc.slack.dummy.com'
CALL_BACK_URL = 'http://localhost:3000'
SIGN_OUT_URL = 'http://localhost:3000'
CLIENT_ID = '<SlackのCLIENT ID>'
CLIENT_SECRET = '<SlackのCLIENT SECRET>'
OIDC_ISSUER = DUMMY_URL
AUTHORIZATION_ENDPOINT = 'https://XXXX.ngrok.io/authorization'# Authorizatin endpointのURL
TOKEN_ENDPOINT = 'https://XXXX.ngrok.io/token' #Token endpointのURL
USERINFO_ENDPOINT = 'https://XXXX.ngrok.io/userinfo' # Userinfo endpointのURL
JWKS_URI = DUMMY_URL

def find_userpool_by_pool_name(client, pool_name):
  """
  ユーザプール名に一致するプールのIDリストを返す。複数一致の可能性あり
  """
  return [pool['Id'] for pool in 
          client.list_user_pools(MaxResults=10)['UserPools'] 
          if pool_name == pool['Name']]

def main():
  client = boto3.client('cognito-idp')

  pool_name = POOL_NAME

  # 前回作ったUserDomainとUserPoolを削除
  for pool_id in find_userpool_by_pool_name(client, pool_name):
    if len(client.describe_user_pool_domain(Domain=DOMAIN_NAME)['DomainDescription']) > 0:
      client.delete_user_pool_domain(Domain=DOMAIN_NAME, UserPoolId=pool_id)
    client.delete_user_pool(UserPoolId=pool_id)

  # ユーザープールの作成
  user_pool = _create_user_pool(client, pool_name)
  user_pool_id = user_pool['UserPool']['Id']
  print('create user pool ', user_pool_id)

  # ドメインの作成
  user_pool_domain = _create_domain(client, DOMAIN_NAME, user_pool_id)
  print('create user pool domain')

  # OIDC Providerを作成
  identity_provider = _create_identity_provider(client, user_pool_id)
  print('create identity provider')
  # アプリクライアントの作成
  client_app = _create_user_pool_app_client(client, user_pool)
  print('create client app')

  client_id = client_app['UserPoolClient']['ClientId']

  # cognitoの認証ページURL
  u = "https://{}.auth.{}.amazoncognito.com/login?response_type=code&client_id={}&redirect_uri={}".format(
    DOMAIN_NAME,
    session.region_name,
    client_id,
    CALL_BACK_URL
  )
  print('cognito auth url: ', u)

def _create_domain(client, domain, user_pool_id):
  return client.create_user_pool_domain(
    Domain=domain,
    UserPoolId=user_pool_id,
  )

def _create_identity_provider(client, user_pool_id):
  return client.create_identity_provider(
      UserPoolId=user_pool_id,
      ProviderName=PROVIDER_NAME,
      ProviderType='OIDC',
      ProviderDetails={
          "client_id": CLIENT_ID,
          "client_secret": CLIENT_SECRET,
          "authorize_scopes": "openid",
          "oidc_issuer": OIDC_ISSUER,
          "attributes_request_method": "GET",
          "authorize_url": AUTHORIZATION_ENDPOINT,
          "attributes_url_add_attributes": "false",
          "token_url": TOKEN_ENDPOINT,
          "attributes_url": USERINFO_ENDPOINT,
          "jwks_uri": OIDC_ISSUER,
      },
      AttributeMapping={
        "email": "email",
      },
      IdpIdentifiers=[
          # 'identify'
      ]
  )

def _create_user_pool(client, pool_name):
  response = client.create_user_pool(
    PoolName=pool_name,
    Policies={
      'PasswordPolicy': {
        'MinimumLength': 8,
        'RequireUppercase': True,
        'RequireLowercase': True,
        'RequireNumbers': True,
        'RequireSymbols': True
      }
    },
    MfaConfiguration='OFF',
    AdminCreateUserConfig={
      'AllowAdminCreateUserOnly': False,
      'UnusedAccountValidityDays': 7,
    },
    Schema=[
      {
        'Name': 'email',
        'AttributeDataType': 'String',
        'DeveloperOnlyAttribute': False,
        'Mutable': True,
        'Required': True,
      },
    ],
  )
  return response


def _create_user_pool_app_client(client, user_pool):
  user_pool_id = user_pool['UserPool']['Id']
  user_pool_name = user_pool['UserPool']['Name']
  response = client.create_user_pool_client(
    UserPoolId=user_pool_id,
    ClientName=f'{user_pool_name}-client',
    RefreshTokenValidity=30,
    SupportedIdentityProviders=[
      PROVIDER_NAME,
    ],
    CallbackURLs=[
      CALL_BACK_URL,
    ],
    LogoutURLs=[
      SIGN_OUT_URL,
    ],
    AllowedOAuthFlows=[
      # 'code'|'implicit'|'client_credentials',
      'code',
    ],
    AllowedOAuthScopes=[
      'openid',
    ],
    AllowedOAuthFlowsUserPoolClient=True,
  )
  return response


def _set_user_pool_mfa_config(client, user_pool):
  user_pool_id = user_pool['UserPool']['Id']
  response = client.set_user_pool_mfa_config(
    UserPoolId=user_pool_id,
    SoftwareTokenMfaConfiguration={
      'Enabled': True # True|False
    },
    MfaConfiguration='ON' # 'OFF'|'ON'|'OPTIONAL'
  )
  return response

if __name__ == '__main__':
  main()

実行

これも元記事同様で以下の方法でいけます。


$ python main.py

create user pool  ap-northeast-1_ZNVHkRgf6
create user pool domain
create identity provider
create client app
cognito auth url:  https://XXXXX.auth.ap-northeast-1.amazoncognito.com/login?response_type=code&client_id=XXXXXXXXXXXX&redirect_uri=http://localhost:3000

ツラツラと標準出力に結果が流れます。
最終行のcognito auth urlのURLを開くとCognitoのログインページになっているので、これを開くとCognitoのログインページが開きます。

あとは設定値が間違っていなければSlackの認可に飛んでCognitoのUserPoolへ登録されます。

処理概要

抜粋して簡単に解説します

やっている処理自体は CognitoIdentityProvider — Boto 3 Docs 1.9.156 documentation のサンプルコードを見れば大体わかったので、軽くハマったところだけ

処理の流れ

ざっくり以下の流れになってます

  1. 前回作成したUser Poolを削除(試行錯誤していたため)
  2. User Pool作成
  3. ドメインの作成
  4. OIDC Providerの作成
  5. クライアントアプリの作成

順番で気をつけるのは以下2つぐらいですかね

  1. User PoolのIDが以降の処理で必要なため最初に実行
  2. クライアントアプリの作成の際にOIDC Providerを指定するので最後じゃないとエラーが出ます。
def main():
  client = boto3.client('cognito-idp')

  pool_name = POOL_NAME

  # 前回作ったUserDomainとUserPoolを削除
  for pool_id in find_userpool_by_pool_name(client, pool_name):
    if len(client.describe_user_pool_domain(Domain=DOMAIN_NAME)['DomainDescription']) > 0:
      client.delete_user_pool_domain(Domain=DOMAIN_NAME, UserPoolId=pool_id)
    client.delete_user_pool(UserPoolId=pool_id)

  # ユーザープールの作成
  user_pool = _create_user_pool(client, pool_name)
  user_pool_id = user_pool['UserPool']['Id']
  print('create user pool ', user_pool_id)

  # ドメインの作成
  user_pool_domain = _create_domain(client, DOMAIN_NAME, user_pool_id)
  print('create user pool domain')

  # OIDC Providerを作成
  identity_provider = _create_identity_provider(client, user_pool_id)
  print('create identity provider')
  # アプリクライアントの作成
  client_app = _create_user_pool_app_client(client, user_pool)
  print('create client app')

ややこしかったところ

ユーザ属性の指定がちょっとわからなかったです

スクリーンショット 2019-05-28 19.19.37.png

どうやったら画像のようにemailを選択した状態になるのかわからず少しハマりましたが、以下のようにSchema属性でした

def _create_user_pool(client, pool_name):
  response = client.create_user_pool(
    PoolName=pool_name,
    Schema=[
      {
        'Name': 'email',
        'AttributeDataType': 'String',
        'DeveloperOnlyAttribute': False,
        'Mutable': True,
        'Required': True,
      },
    ],
  )
  return response

ちなみに

一番盛大にハマったのはOIDC Providerの設定です。

最終的に以下のようなコードになったのですが

def _create_identity_provider(client, user_pool_id):
  return client.create_identity_provider(
      UserPoolId=user_pool_id,
      ProviderName=PROVIDER_NAME,
      ProviderType='OIDC',
      ProviderDetails={
          "client_id": CLIENT_ID,
          "client_secret": CLIENT_SECRET,
          "authorize_scopes": "openid",
          "oidc_issuer": OIDC_ISSUER,
          "attributes_request_method": "GET",
          "authorize_url": AUTHORIZATION_ENDPOINT,
          "attributes_url_add_attributes": "false",
          "token_url": TOKEN_ENDPOINT,
          "attributes_url": USERINFO_ENDPOINT,
          "jwks_uri": OIDC_ISSUER,
      },
      AttributeMapping={
        "email": "email",
      },
      IdpIdentifiers=[
          # 'identify'
      ]
  )

Boto3ドキュメントのCognitoIdentityProvider — Boto 3 Docs 1.9.156 documentation には以下のような記載になっています。

response = client.create_identity_provider(
    UserPoolId='string',
    ProviderName='string',
    ProviderType='SAML'|'Facebook'|'Google'|'LoginWithAmazon'|'OIDC',
    ProviderDetails={
        'string': 'string'
    },
    AttributeMapping={
        'string': 'string'
    },
    IdpIdentifiers=[
        'string',
    ]
)

えっ?ProviderDetailsは何をキーにすれば良いの?

って感じで調べたんですが、なかなかドキュメントが発見できず。。。

結局手動で操作した時の通信内容をブラウザのデバッガーで確認するという曲芸技に走りました。これで無事動きました。

firefoxdebug.png

そしてその後ドキュメントの記載を見つけるという。。。

ユーザープールへの OIDC ID プロバイダーの追加 - Amazon Cognito

このページ内で折りたたまれているOIDC IdP を追加するには (AWS CLI)を開けると書いてありました。

開くまではページ内検索をしても引っかからない、Googleの検索結果にも表示されない

これは無理ゲーだった

参考

CognitoIdentityProvider — Boto 3 Docs 1.9.156 documentation
PythonでAmazon Cognitoのユーザープールを作成してみる - Qiita
ユーザープールへの OIDC ID プロバイダーの追加 - Amazon Cognito

4
4
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
4
4