Cognito User Pools と OIDC で Slack サインインを実装する
Cognito user pool を OIDC 経由で Slack と連携させ、"Sign in with Slack" を Amplify で Next.js アプリケーションに組み込みます。
Cognito user pools には Slack 専用のプロバイダーがないため、OIDC 経由の汎用アイデンティティプロバイダーとして Slack と連携し、Next.js アプリケーションに “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 のフロントエンド API を使って、Cognito のリダイレクトフローを開始し、認証済みユーザーを取得します。
プロジェクトディレクトリに Amplify をインストールします。
cd my-appnpm i aws-amplifyバックエンドの構築
Slack アプリの作成
Cognito User Pool で Slack を OIDC アイデンティティプロバイダーとして使うため、Slack アプリを作成します。
Slack ワークスペースのメニューを開き、Manage apps に移動します。

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

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

プロンプトが表示されたら、From an app manifest を選択します。
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 を設定する前に安全な場所へ保存します。
ステップ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: 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 に表示されるようになりました。
この連携では、2 種類のスコープを別々に扱います。Cognito は上流の OIDC プロバイダーである Slack に openid、email、profile を要求します。一方、Next.js アプリケーションは Cognito の認可サーバーに対して、ユーザープールのセルフサービス API に必要な aws.cognito.signin.user.admin などを要求します。Slack の OIDC 用ではない users:read スコープは不要であり、同じ Sign in with Slack の認可リクエストに混在させることもできません。
Slack のクライアントシークレットを Secrets Manager に保存し、slackSecretArn を通じて参照すれば、CDK のソースコードへ認証情報を直接書かずに済みます。通常の認証情報管理と同様に、合成したテンプレート、デプロイログ、シークレットへのアクセスも制限してください。
Related posts
Microsoft Entra ID と Amplify による Cognito SAML サインイン
Amazon Cognito ユーザープールと Microsoft Entra ID の間に SAML サインインを設定し、Amplify を使った Next.js アプリから利用します。
Lambda Web Adapter で FastAPI を AWS Lambda にデプロイする
FastAPI で書いた API バックエンドをコンテナ化し、Lambda Web Adapter と AWS CDK を使って単一の 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 を直接使えない場合に有用です。
