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が存在する場合は削除するようになっているので、既にある場合は注意してください
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 のサンプルコードを見れば大体わかったので、軽くハマったところだけ
処理の流れ
ざっくり以下の流れになってます
- 前回作成したUser Poolを削除(試行錯誤していたため)
- User Pool作成
- ドメインの作成
- OIDC Providerの作成
- クライアントアプリの作成
順番で気をつけるのは以下2つぐらいですかね
- User PoolのIDが以降の処理で必要なため最初に実行
- クライアントアプリの作成の際に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')
ややこしかったところ
ユーザ属性の指定がちょっとわからなかったです
どうやったら画像のように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
は何をキーにすれば良いの?
って感じで調べたんですが、なかなかドキュメントが発見できず。。。
結局手動で操作した時の通信内容をブラウザのデバッガーで確認するという曲芸技に走りました。これで無事動きました。
そしてその後ドキュメントの記載を見つけるという。。。
ユーザープールへの 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