Slack でのサインインを Cognito ユーザープールで実現する方法
はじめに
Amazon Cognitoユーザープールは、OpenID Connect (OIDC) を使用したフェデレーション認証を可能にします。これにより、アプリケーションのユーザーは、好きなサードパーティアカウントでログインできます。
この投稿では、Slack を Amazon Cognito の OpenID Connect プロバイダーとして設定するプロセスを解説します。AWS CDK を使用してバックエンドを構築し、React/Next.js と AWS Amplify を使用して、Sign in with Slack 機能のフロントエンドを作成します。完全な例は この GitHub リポジトリ をクローンして確認できます。
プロジェクト構成
この記事で構築するファイル構成は以下の通りです。
|-- cdk
| |-- bin
| | `-- cdk.ts
| |-- lib
| | `-- cdk-stack.ts
| |-- node_modules
| |-- test
| | `-- cdk.test.ts
| |-- .gitignore
| |-- .npmignore
| |-- biome.json
| |-- cdk.json
| |-- jest.config.js
| |-- package.json
| |-- package-lock.json
| |-- README.md
| `-- tsconfig.json
|-- my-app
| |-- node_modules
| |-- public
| |-- src
| | |-- app
| | | |-- globals.css
| | | |-- layout.tsx
| | | `-- page.tsx
| | `-- auth.ts
| |-- .env.local
| |-- .gitignore
| |-- biome.json
| |-- next.config.mjs
| |-- next-env.d.ts
| |-- package.json
| |-- package-lock.json
| |-- postcss.config.mjs
| |-- README.md
| |-- tailwind.config.ts
| `-- tsconfig.json
|-- node_modules
|-- .gitignore
|-- LICENSE
|-- package.json
`-- package-lock.json
はじめの準備
AWS CDK 環境のブートストラップ
まず、AWS CDK 環境をブートストラップします。このステップを既に終えている場合はスキップできます。
以下のコマンドを実行して AWS CDK をローカルにインストールし、環境をブートストラップします:
npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>
CDK プロジェクトの初期化
CDK プロジェクト用のディレクトリを作成して初期化します:
mkdir cdk && cd cdk
npx cdk init app --language typescript
React/Next.js のインストール
アプリケーションのフロントエンドを作成するために、以下のコマンドを実行して React/Next.js をインストールします:
npx create-next-app@latest
セットアップ中、以下のプロンプトに答えてください:
- Project name:
my-app
- Use TypeScript:
Yes
- Use ESLint:
No
- Use Tailwind CSS:
Yes
- Use
src/
directory:Yes
- Use App Router:
Yes
- Customize default import alias (@/*):
No
これで必要な依存関係を含む Next.js アプリが初期化されます。
AWS Amplify のインストール
AWS Amplify は、Amazon Cognito とのやり取りを簡単にし、認証を効率的に管理することができます。
プロジェクトディレクトリで Amplify をインストールします:
cd my-app
npm i aws-amplify
インストールが完了したら、React/Next.js アプリに AWS Amplify を統合して、認証とユーザー管理を行えるようになります。
バックエンドの構築
Slack アプリの作成
Slack の認証を Cognito ユーザープールで有効にするために、新しい Slack アプリを作成する必要があります。以下の手順に従ってください:
1. アプリ管理画面を開く
Slack ワークスペースのメニューを開き、Manage Apps
に進みます。
2. 新しいアプリを作成する
Slack アプリディレクトリページで Build
ボタンを押します。
3. アプリを作成する
アプリのページで Create an App
ボタンを押します。
4. アプリマニフェストを選択
プロンプトが表示されたら、From an app manifest
オプションを選択します。これにより、Slack アプリの作成と設定が簡単になります。
Use the app manifest system to quickly create, configure, and reuse Slack app configurations.
5. ワークスペースを選択
アプリがインストールされるワークスペースを選択します。
6. アプリに名前を付ける
アプリに名前を付けます。この投稿では Sign in with Slack
を使用します。他のフィールドはデフォルトのままにします。
7. 作成プロセスを完了する
Create
ボタンを押して、Slack アプリのセットアップを完了します。
これで、Amazon Cognito との統合のために Slack アプリが準備されました。
Slack アプリの資格情報を確認
Slack アプリを Cognito ユーザープールと統合するには、アプリの資格情報を取得して安全に保存する必要があります。以下の手順に従ってください:
1. Slack アプリ資格情報を取得する
Slack アプリの 基本情報 ページに移動し、以下の値を見つけてください:
- クライアント ID
- クライアントシークレット
これらの資格情報は後で Cognito ユーザープールを設定する際に使用します。
2. AWS Secrets Manager に資格情報を保存する
セキュリティを強化するため、クライアント ID と クライアントシークレット を AWS Secrets Manager に保存し、AWS CDK コードに直接埋め込まないようにします。
以下のコマンドを実行して AWS Secrets Manager に新しいシークレットを作成します:
aws secretsmanager create-secret \
--name sign-in-with-slack \
--secret-string '{"clientId": "<YOUR_CLIENT_ID>", "clientSecret": "<YOUR_CLIENT_SECRET>"}'
<YOUR_CLIENT_ID>
と <YOUR_CLIENT_SECRET>
を Slack アプリから取得した実際の値に置き換えてください。
これで、Slack アプリ資格情報が安全に保存され、Cognito ユーザープールの設定で参照可能になりました。
Cognito ユーザープールの作成
Slack を Cognito と統合するには、Cognito ユーザープールを設定し、AWS Secrets Manager に保存した Slack アプリ資格情報をリンクします。
ステップ 1: cdk/bin/cdk.ts
を更新
cdk/bin/cdk.ts
ファイルに以下のコードを追加して、必要な環境変数(SLACK_SECRET_ARN
と COGNITO_DOMAIN_PREFIX
)を実行時に渡します:
#!/usr/bin/env node
import '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 ユーザープールを設定
cdk/lib/cdk-stack.ts
に以下のコードを貼り付けます。
ポイント:
- ユーザー属性を取得するために
UserPoolClient.oAuth.scopes
にOAuthScope.COGNITO_ADMIN
を含めます(55行目)。詳細は 公式ドキュメント をご参照ください。 - AWS Secrets Manager から Slack の資格情報を安全に取得します(65-69行目)。
- 資格情報がコード内に露出しないようにします(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 deploy
これで、Cognito ユーザープールが準備され、Slack を OIDC プロバイダーとして利用できるようになりました。
Slack アプリの OAuth 設定
Slack と Cognito の統合を完了するために、Slack アプリの OAuth 設定を構成します。
ステップ 1: リダイレクト URL の設定
Slack アプリの設定ページで OAuth & Permissions に移動し、Cognito のリダイレクト URL を追加します:
Add New Redirect URL
ボタンを押します。- 次の URL を入力します。
<COGNITO_DOMAIN_PREFIX>
を実際のドメインプレフィックスに置き換えてください:
https://<COGNITO_DOMAIN_PREFIX>.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse
Save URLs
ボタンを押します。
Register your user pool domain URL with the
/oauth2/idpresponse
endpoint with your OIDC IdP.
Cognito ドメインが不明な場合は、Cognito ユーザープールの App Integration タブで確認してください:
ステップ 2: OAuth スコープを追加
Slack アプリの OAuth & Permissions ページで必要なスコープを追加します:
- User Token Scopes に
users:read
を追加します。 - 変更を保存します。
ステップ 3: Slack アプリのインストール
Install to <WORKSPACE>
ボタンをクリックします。- プロンプトに従ってインストールを完了します。
Slackでのサインインのテスト
Slack でのサインインのフローをテストして、統合が正常に動作することを確認します。
ステップ 1: Cognito ホステッド UI にアクセス
- Cognito の アプリクライアント ページに移動します。
View Hosted UI
ボタンを押します。
ステップ 2: Slack ログインを開始
- ホステッド UI 上で
Slack
ボタンを押します。
- ワークスペース名を入力して
Continue
を押します。
- Slack のログインプロセスを完了し、
Allow
を押してアクセスを許可します。
ステップ 3: コールバック URL を確認
ログインが成功すると、Cognito アプリクライアントで設定されたコールバック URL にリダイレクトされます。この投稿では、デフォルト URL は https://example.com/
です。
ステップ 4: Cognito 内でのフェデレートユーザーの確認
以下のコマンドを実行して、フェデレートユーザーが Cognito ユーザープールに追加されていることを確認します:
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"
}
]
}
これで、Sign in with Slack が完全に設定され、正常に動作するようになりました。
React/Next.js アプリの構築
このセクションでは、Cognito と Slack を使用した認証のために React/Next.js アプリケーションを設定する方法を説明します。
環境変数の設定
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://
で始めないことを確認してください。
プレースホルダー(<COGNITO_USER_POOL_ID>
、<COGNITO_USER_POOL_CLIENT_ID>
、<COGNITO_DOMAIN_PREFIX>
)を、Cognito セットアップから取得した実際の値に置き換えてください。
認証ヘルパーの作成
認証を管理するために、アプリの src
ディレクトリに auth.ts
ファイルを作成し、以下の関数を実装します (./my-app/src/auth.ts
):
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 session
export async function authSession(): Promise<AuthSession> {
return await fetchAuthSession();
}
// Fetch the currently authenticated user
export async function authCurrentUser(): Promise<AuthUser> {
return await getCurrentUser();
}
// Fetch user attributes
export async function fetchAttributes(): Promise<AuthUserAttributes> {
// Requires the 'aws.cognito.signin.user.admin' scope
return await fetchUserAttributes();
}
// Redirect to the Slack sign-in page
export async function authSignIn(): Promise<void> {
await signInWithRedirect({
provider: { custom: 'Slack' },
});
}
// Sign out the user
export async function authSignOut(): Promise<void> {
await signOut();
}
ポイント:
- スコープ:
oauth.scopes
にaws.cognito.signin.user.admin
(27行目)を含める必要があります。これにより、fetchUserAttributes
関数が利用できます。- 詳細は Cognito リソースサーバードキュメント をご参照ください。
- リダイレクト URL:
oauth.redirectSignIn
とoauth.redirectSignOut
の値(31-32行目)は、Cognito アプリクライアントに設定された URL と 完全に一致 している必要があります(末尾のスラッシュを含む)。
ホームコンポーネント
ユーザー情報を表示し、サインインとサインアウトアクションを処理するために Home
コンポーネントを実装します。./my-app/src/app/page.tsx
ファイルを作成し、以下のコードを追加します:
'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>
);
}
主要な機能:
- 認証処理(20-25行目):
- 有効なセッショントークンをチェックします。
- セッションがない場合はサインインを開始します。
- Email やユーザー名などのユーザー属性を取得します。
- サインアウト機能(49-55行目):
- サインアウトアクションを処理するボタンを含みます。
- ユーザー情報(33-46行目):
- Tailwind CSS を使用して、構造化された形式でユーザー情報を表示します。
Next.js アプリのテスト
アプリをテストするには、ローカル開発サーバーを起動します:
npm run dev
> my-app@0.1.0 dev
> next dev
▲ Next.js 14.2.5
- Local: http://localhost:3000
- Environments: .env.local
✓ Starting...
✓ Ready in 1507ms
- ブラウザで
http://localhost:3000
にアクセスします。 - Slack サインインページにリダイレクトされるはずです。
- サインインプロセスを完了します。
- サインインが成功すると、ユーザー名、ユーザーID、Email がアプリに表示されます。
まとめ
“Sign in with Slack” は、エンタープライズアプリケーションにおけるユーザー体験 (UX) を向上させる強力なツールです。Slack を Amazon Cognito と統合することで、複雑な認証ロジックをオフロードし、アプリケーションのコア機能に集中できます。
主なメリット:
- シームレスなユーザー体験: ユーザーが Slack アカウントを使用して認証可能。
- スケーラビリティ: Cognito が認証ワークフローを大規模に処理。
- セキュリティの向上: AWS の安全なインフラストラクチャを活用。
Azure Entra ID を使用した認証については、以下の記事をご覧ください:
Happy Coding! 🚀