Cognito User PoolsとOIDCでSlackサインインを実装する
Cognito user poolをOIDC経由でSlackと連携させ、"Sign in with Slack"をAmplifyでNext.jsアプリに組み込みます。
Cognito user poolsは名前付きのSlackプロバイダーを持たないため、OIDC経由の汎用アイデンティティプロバイダーとして連携させることで、“Sign in with Slack”を実装します。
はじめに
AWS CDK環境のブートストラップ
AWS CDK環境がまだブートストラップされていなければ、次のコマンドでローカルにCDKをインストールし、環境をブートストラップします。すでに済んでいる場合はこの手順は不要です。
npm i -D aws-cdknpx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>CDKプロジェクトの初期化
CDKプロジェクト用のディレクトリを作成し、初期化します。
mkdir cdk && cd cdknpx cdk init app --language typescriptReact/Next.jsのインストール
アプリケーションのフロントエンド用に、React/Next.jsをインストールします。
npx create-next-app@latestセットアップ中は、次のようにプロンプトに答えます。
- プロジェクト名:
my-app - TypeScriptを使用する:
Yes - ESLintを使用する:
No - Tailwind CSSを使用する:
Yes src/ディレクトリを使用する:Yes- App Routerを使用する:
Yes - デフォルトのインポートエイリアス(@/*)をカスタマイズする:
No
AWS Amplifyのインストール
AWS Amplifyを使うと、Cognitoとのやり取りが簡素化され、認証を簡単に管理できます。
プロジェクトディレクトリにAmplifyをインストールします。
cd my-appnpm i aws-amplifyバックエンドの構築
Slackアプリの作成
Cognito User PoolsでSlack認証を有効にするには、新しいSlackアプリを作成する必要があります。
Slackワークスペースのメニューを開き、Manage appsに移動します。

Slackアプリディレクトリのページで、Buildボタンを押します。

アプリのページで、Create an Appボタンをクリックします。

プロンプトが表示されたら、From an app manifestオプションを選択します。これにより、Slackアプリの作成と設定を簡単に行えます。
https://api.slack.com/reference/manifests
Use the app manifest system to quickly create, configure, and reuse Slack app configurations.

アプリをインストールするワークスペースを選択します。

アプリの名前を入力します。この記事ではSign in with Slackを使用します。他のフィールドはデフォルトのままにしておきます。

Createボタンを押してSlackアプリのセットアップを完了します。

Slackアプリの認証情報の確認
Slackアプリと Cognito User Poolsを統合するには、アプリの認証情報を取得し、安全に保管する必要があります。
ステップ1. Slackアプリの認証情報を取得する
SlackアプリのBasic Informationページに移動し、Client IDとClient Secretの値をコピーします。
これらの認証情報は、後でCognito User Poolを設定する際に使用します。

ステップ2. AWS Secrets Managerに認証情報を保存する
セキュリティを高めるため、Client IDとClient SecretをAWS CDKのコードに直接埋め込むのではなく、AWS Secrets Managerに保存します。
AWS CDKファイルにSlackの認証情報をハードコーディングするのは避けてください。常にAWS Secrets Managerのような安全なストレージを使用してください。
aws secretsmanager create-secret \ --name sign-in-with-slack \ --secret-string '{"clientId": "<YOUR_CLIENT_ID>", "clientSecret": "<YOUR_CLIENT_SECRET>"}'Cognito User Poolの作成
SlackとCognitoを統合するため、Cognito User Poolを設定し、AWS Secrets Managerに保存したSlackアプリの認証情報と紐付けます。
ステップ1: cdk/bin/cdk.tsを更新する
実行時に必要な環境変数(SLACK_SECRET_ARNとCOGNITO_DOMAIN_PREFIX)を渡すため、cdk/bin/cdk.tsファイルに次のコードを追加します。
#!/usr/bin/env nodeimport 'source-map-support/register';import * as cdk from 'aws-cdk-lib';import { CdkStack } from '../lib/cdk-stack';
const app = new cdk.App();new CdkStack(app, 'CdkStack', { slackSecretArn: process.env.SLACK_SECRET_ARN ?? '', cognitoDomainPrefix: process.env.COGNITO_DOMAIN_PREFIX ?? '',});ステップ2: cdk/lib/cdk-stack.tsでCognito User Poolを設定する
cdk/lib/cdk-stack.tsは次のようになります。
- ユーザー属性を取得するため、
UserPoolClient.oAuth.scopesにOAuthScope.COGNITO_ADMINを含めます(55行目)。詳細は公式ドキュメントを参照してください。 - AWS Secrets ManagerからSlackの認証情報を安全に取得します(65〜69行目)。
- Slackの認証情報はコードに露出させることなく注入されます(75〜82行目)。
import * as cdk from 'aws-cdk-lib';import type { Construct } from 'constructs';import { OAuthScope, ProviderAttribute, UserPool, UserPoolClient, UserPoolClientIdentityProvider, UserPoolDomain, UserPoolIdentityProviderOidc,} from 'aws-cdk-lib/aws-cognito';import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
export class CdkStack extends cdk.Stack { constructor( scope: Construct, id: string, props?: cdk.StackProps & { slackSecretArn: string; cognitoDomainPrefix: string; }, ) { super(scope, id, props);
// Cognito User Pool const userPool = new UserPool(this, 'user-pool', { userPoolName: 'sign-in-with-slack-user-pool', });
// Cognito User Pool Domain new UserPoolDomain(this, 'user-pool-domain', { userPool, cognitoDomain: { domainPrefix: props?.cognitoDomainPrefix ?? '', }, });
// Cognito User Pool Client new UserPoolClient(this, 'user-pool-client', { userPool, userPoolClientName: 'client', oAuth: { flows: { authorizationCodeGrant: true, }, callbackUrls: [ 'https://example.com/', // Cognito app client default 'http://localhost:3000/', ], logoutUrls: ['http://localhost:3000/'], scopes: [ OAuthScope.OPENID, OAuthScope.EMAIL, OAuthScope.PROFILE, OAuthScope.COGNITO_ADMIN, ], }, supportedIdentityProviders: [ UserPoolClientIdentityProvider.COGNITO, UserPoolClientIdentityProvider.custom('Slack'), ], });
// Slack app credentials stored in your Secrets Manager const slackSecret = Secret.fromSecretCompleteArn( this, 'slack-secret', props?.slackSecretArn ?? '', );
// Cognito User Pool Identity Provider (OIDC) new UserPoolIdentityProviderOidc(this, 'slack-oidc', { userPool, name: 'Slack', clientId: slackSecret .secretValueFromJson('clientId') .unsafeUnwrap() .toString(), clientSecret: slackSecret .secretValueFromJson('clientSecret') .unsafeUnwrap() .toString(),
// See https://api.slack.com/authentication/sign-in-with-slack#request // > Which permissions you want the user to grant you. // > Your app will request openid, the base scope you always need to request in any Sign in with Slack flow. // > You may request email and profile as well. scopes: ['openid', 'email', 'profile'],
// See https://api.slack.com/authentication/sign-in-with-slack#discover issuerUrl: 'https://slack.com',
// The following endpoints do not need to be configured because the Cognito can find them by the issuer url. // endpoints: { // authorization: 'https://slack.com/openid/connect/authorize', // token: 'https://slack.com/api/openid.connect.token', // userInfo: 'https://slack.com/api/openid.connect.userInfo', // jwksUri: 'https://slack.com/openid/connect/keys', // },
attributeMapping: { email: ProviderAttribute.other('email'), profilePage: ProviderAttribute.other('profile'), }, }); }}ステップ3: スタックをデプロイする
デプロイする前に、プレースホルダーの環境変数を実際の値に置き換えます。
SLACK_SECRET_ARN: Slackの認証情報を含むSecrets ManagerエントリのARN。COGNITO_DOMAIN_PREFIX: Cognitoドメイン用の一意なプレフィックス。
次のコマンドを実行してデプロイします。
export SLACK_SECRET_ARN=arn:aws:secretsmanager:<AWS_REGION>:<AWS_ACCOUNT_ID>:secret:sign-in-with-slack-<SUFFIX>export COGNITO_DOMAIN_PREFIX=<ANY_PREFIX_YOU_LIKE>npx cdk deploySlackアプリの OAuth設定
SlackとCognitoの統合を仕上げる最後のステップとして、SlackアプリのOAuth設定を行います。
ステップ1: リダイレクトURLを設定する
Slackアプリの設定にあるOAuth & Permissionsページに移動し、CognitoのリダイレクトURLを追加します。
Add New Redirect URLボタンを押します。- 次のURLを入力し、
<COGNITO_DOMAIN_PREFIX>を実際のドメインプレフィックスに置き換えます。
Using OIDC identity providers with a user poolhttps://<COGNITO_DOMAIN_PREFIX>.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse
Register your user pool domain URL with the
/oauth2/idpresponseendpoint with your OIDC IdP. Save URLsボタンを押します。

Cognitoドメインが分からない場合は、Cognito User PoolのApp Integrationタブで確認できます。

ステップ2: OAuthスコープを追加する
OAuth & Permissionsページで、User Token Scopesの下に必要なスコープusers:readを追加します。変更を保存して完了します。

ステップ3: Slackアプリをインストールする
Install to <WORKSPACE>ボタンをクリックし、プロンプトに従ってインストールを完了します。


Sign in with Slackのテスト
CognitoのApp Clientページに移動し、View Hosted UIボタンを押します。

Hosted UIで、Slackボタンを押します。

ワークスペース名を入力し、Continueを押します。

Slackのログイン処理を完了し、Allowを押してアクセスを許可します。


ログインが成功すると、Cognitoアプリクライアントで設定されたコールバックURLにリダイレクトされます。この例では、デフォルトはhttps://example.com/です。

次のコマンドを実行して、連携されたユーザーがCognito User Poolに追加されていることを確認します。
aws cognito-idp list-users \ --user-pool-id <COGNITO_USER_POOL_ID>出力例:
{ "Users": [ { "Username": "Slack_U07G7NBRPN2", "Attributes": [ { "Name": "email", "Value": "<SLACK_USER_EMAIL>" }, { "Name": "email_verified", "Value": "false" }, { "Name": "sub", "Value": "<UUID>" }, { "Name": "identities", "Value": "<SLACK_IDENTITIES>" } ], "UserCreateDate": "2024-08-12T15:12:47.047000+09:00", "UserLastModifiedDate": "2024-08-12T15:12:47.047000+09:00", "Enabled": true, "UserStatus": "EXTERNAL_PROVIDER" } ]}フロントエンドの構築
CognitoおよびSlackでの認証に対応するよう、React/Next.jsアプリケーションを設定します。
Dot Env設定
Next.jsアプリのルートディレクトリに.env.localファイル(./my-app/.env.local)を作成し、次の値を追加します。
NEXT_PUBLIC_USER_POOL_ID=<COGNITO_USER_POOL_ID>NEXT_PUBLIC_USER_POOL_CLIENT_ID=<COGNITO_USER_POOL_CLIENT_ID>NEXT_PUBLIC_OAUTH_DOMAIN=<COGNITO_DOMAIN_PREFIX>.auth.ap-northeast-1.amazoncognito.com
NEXT_PUBLIC_OAUTH_DOMAIN(3行目)がhttps://で始まっていないことを確認してください。
Authヘルパーの作成
認証を管理するため、アプリのsrcディレクトリにauth.ts(./my-app/src/auth.ts)を作成し、次の関数を実装します。
- スコープ:
fetchUserAttributes関数を有効にするため、oauth.scopesにはaws.cognito.signin.user.adminを含める必要があります(27行目)。詳細は公式ドキュメントを参照してください。
- リダイレクトURL:
oauth.redirectSignInとoauth.redirectSignOutの値(31〜32行目)が、末尾のスラッシュも含めてCognitoアプリクライアントの設定と完全に一致することを確認してください。
import { Amplify } from 'aws-amplify';import { type AuthSession, fetchAuthSession, fetchUserAttributes, getCurrentUser, signInWithRedirect, signOut,} from 'aws-amplify/auth';import type { AuthConfig } from '@aws-amplify/core';import type { AuthUser } from '@aws-amplify/auth';import type { AuthUserAttributes } from '@aws-amplify/auth/dist/esm/types';
const authConfig: AuthConfig = { Cognito: { userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID ?? '', userPoolClientId: process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID ?? '', loginWith: { oauth: { // Ensure this does not start with "https://" domain: process.env.NEXT_PUBLIC_OAUTH_DOMAIN ?? '', scopes: [ 'openid', 'email', 'profile', // Required for the `fetchUserAttributes` function 'aws.cognito.signin.user.admin', ], providers: [{ custom: 'Slack' }], // Redirect URLs must match Cognito app client configuration exactly, including the trailing slash. redirectSignIn: ['http://localhost:3000/'], redirectSignOut: ['http://localhost:3000/'], responseType: 'code', }, }, },};Amplify.configure({ Auth: authConfig });
// Fetch the current sessionexport async function authSession(): Promise<AuthSession> { return await fetchAuthSession();}
// Fetch the currently authenticated userexport async function authCurrentUser(): Promise<AuthUser> { return await getCurrentUser();}
// Fetch user attributesexport async function fetchAttributes(): Promise<AuthUserAttributes> { // Requires the 'aws.cognito.signin.user.admin' scope return await fetchUserAttributes();}
// Redirect to the Slack sign-in pageexport async function authSignIn(): Promise<void> { await signInWithRedirect({ provider: { custom: 'Slack' }, });}
// Sign out the userexport async function authSignOut(): Promise<void> { await signOut();}Homeコンポーネント
ユーザー情報を表示し、サインイン・サインアウトの操作を処理するため、Homeコンポーネントを実装します。次のコードで./my-app/src/app/page.tsxファイルを作成します。
- 認証処理(20〜25行目):
- 有効なセッショントークンを確認します。
- セッションが見つからない場合はサインインを開始します。
- メールアドレスやユーザー名などのユーザー属性を取得します。
- サインアウト機能(49〜55行目):
- サインアウト操作を処理するボタンを含みます。
- ユーザー情報(33〜46行目):
- ユーザー情報を表示します。
'use client';
import { useEffect, useState } from 'react';import { authSession, authSignOut, authSignIn, authCurrentUser, fetchAttributes,} from '@/auth';import type { AuthUser } from '@aws-amplify/auth';import type { AuthUserAttributes } from '@aws-amplify/auth/dist/esm/types';
export default function Home() { const [user, setUser] = useState<AuthUser>(); const [attributes, setAttributes] = useState<AuthUserAttributes>();
useEffect(() => { (async () => { const session = await authSession(); if (session.tokens) { setUser(await authCurrentUser()); setAttributes(await fetchAttributes()); } else { await authSignIn(); } })(); }, []);
return ( <div className="flex flex-col items-center w-full h-screen max-w-screen-md mx-auto mt-8"> <div className="flex flex-col gap-4"> <div className="flex gap-2"> <div className="w-20">Username:</div> <div>{user?.username}</div> </div>
<div className="flex gap-2"> <div className="w-20">User ID:</div> <div>{user?.userId}</div> </div>
<div className="flex gap-2"> <div className="w-20">Email:</div> <div>{attributes?.email}</div> </div>
<div className="self-end"> <button type="button" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" onClick={authSignOut} > Sign out </button> </div> </div> </div> );}Next.jsアプリのテスト
ローカル開発サーバーを起動します。
npm run dev
> [email protected] dev> next dev
▲ Next.js 14.2.5 - Local: http://localhost:3000 - Environments: .env.local
✓ Starting... ✓ Ready in 1507msブラウザを開き、http://localhost:3000にアクセスします。Slackのサインインページにリダイレクトされるはずです。サインインに成功すると、アプリがユーザー情報を表示します。

まとめ
OIDC経由でCognito user poolをSlackと連携させ、AmplifyでNext.jsアプリに組み込んだことで、「Sign in with Slack」がエンドツーエンドで動作し、連携されたユーザーがCognito user poolに表示されるようになりました。CognitoはSlackを名前付きのソーシャルIDプロバイダーではなく汎用的なOIDCプロバイダーとして扱うため、この統合の要点は2つの一致に集約されます。1つはaws.cognito.signin.user.adminとusers:readスコープがそれぞれの側で一致していること、もう1つはSlackのOAuth設定とCognitoアプリクライアントの間でリダイレクトURLが末尾のスラッシュも含めて一致していることです。Slackのクライアントシークレットを、CDKスタックにハードコーディングするのではなくSecrets Managerに保存しslackSecretArn経由で注入するという方法は、この特定の統合にとどまらず持ち帰る価値のある実践です。これにより、どのIDプロバイダーと連携していてもCDKのコード自体は安全にコミットできる状態を保てます。
Related posts
Azure Entra IDとAmplifyによるCognito SAMLサインイン
AWS Amplify、Cognitoユーザープール、Azure Entra IDを使ったSAML認証について解説します。
Lambda Web AdapterでFastAPIをAWS Lambdaにデプロイする
Lambda Web Adapterを使うと、FastAPIで書いたAPIバックエンドをコンテナのまま単一のLambda関数にデプロイできます。

AWS Amplify と Eclipse Mosquitto を連携させた MQTT メッセージング
AWS Amplify の PubSub モジュールを AWS IoT Core に接続する前に、ローカルの Eclipse Mosquitto ブローカーでテストする方法。
API Gateway WebSocket:モック統合の実装
バックエンドのLambdaを一切使わず、モック統合のみでAPI Gateway WebSocket APIを構築し、あらかじめ用意されたレスポンスを返します。
CloudFront署名付きURL経由でS3にアップロードする
CloudFrontの署名付きURLを使えば、独自ドメイン経由でS3にアップロードできます。S3の直接の署名付きURLが使えない場合に有用です。
