Sign in with Slack Using Cognito User Pools
Introduction
Amazon Cognito User Pools enable federated authentication through OpenID Connect (OIDC). This means your application users can log in with their preferred third-party accounts.
In this post, we’ll walk you through the process of setting up Slack as an OpenID Connect provider for Amazon Cognito. Using AWS CDK, you’ll build the backend, and with React/Next.js and AWS Amplify, you’ll create the frontend for the “Sign in with Slack” feature. You can clone the full example from this GitHub repository.
Project Structure
Here’s the file structure you’ll build in this post:
|-- 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
Getting Started
Bootstrapping AWS CDK Environment
First, bootstrap your AWS CDK environment. If you’ve already done this, you can skip this step.
Run the following commands to install AWS CDK locally and bootstrap your environment:
npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>
Initializing a CDK Project
Create a directory for your CDK project and initialize it:
mkdir cdk && cd cdk
npx cdk init app --language typescript
Installing React/Next.js
To create the frontend for your application, install React/Next.js by running the following command:
npx create-next-app@latest
During the setup, answer the prompts as follows:
- 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
This will initialize a Next.js app with the necessary dependencies.
Installing AWS Amplify
AWS Amplify simplifies the interaction with Amazon Cognito, allowing you to manage authentication with ease.
Install Amplify in your project directory:
cd my-app
npm i aws-amplify
Once installed, you are ready to integrate AWS Amplify into your React/Next.js application for authentication and user management.
Building Backend
Creating a Slack App
To enable Slack authentication with Cognito User Pools, you’ll need to create a new Slack app. Follow these steps:
1. Navigate to Manage Apps
Open your Slack workspace menu and go to Manage Apps
.
2. Build a New App
On the Slack app directory page, press the Build
button.
3. Create an App
On your apps page, click the Create an App
button.
4. Choose App Manifest Option
When prompted, select the From an App Manifest
option. This simplifies the process of creating and configuring your Slack app.
Use the app manifest system to quickly create, configure, and reuse Slack app configurations.
5. Select Your Workspace
Choose the workspace where your app will be installed.
6. Name Your App
Provide a name for your app. In this post, we use Sign in with Slack
. Leave the other fields as default.
7. Complete the Creation Process
Press the Create
button to finalize the setup of your Slack app.
Your Slack app is now ready to be configured for integration with Amazon Cognito.
Checking Slack App Credentials
To integrate your Slack app with Cognito User Pools, you will need to retrieve and store the app credentials securely. Follow these steps:
1. Retrieve Slack App Credentials
Navigate to your Slack app’s Basic Information page and locate the following values:
- Client ID
- Client Secret
These credentials will be used to configure the Cognito User Pool later.
2. Store Credentials in AWS Secrets Manager
To enhance security, store the Client ID and Client Secret in AWS Secrets Manager instead of embedding them directly in your AWS CDK code.
Run the following command to create a new secret in AWS Secrets Manager:
aws secretsmanager create-secret \
--name sign-in-with-slack \
--secret-string '{"clientId": "<YOUR_CLIENT_ID>", "clientSecret": "<YOUR_CLIENT_SECRET>"}'
Replace <YOUR_CLIENT_ID>
and <YOUR_CLIENT_SECRET>
with the actual values retrieved from your Slack app.
Your Slack app credentials are now securely stored and ready to be referenced in the Cognito User Pool configuration.
Creating Cognito User Pool
To integrate Slack with Cognito, you’ll configure a Cognito User Pool and link it with the Slack app credentials stored in AWS Secrets Manager.
Step 1: Update cdk/bin/cdk.ts
Add the following code to the cdk/bin/cdk.ts
file to pass the required environment variables (SLACK_SECRET_ARN
and COGNITO_DOMAIN_PREFIX
) at runtime:
#!/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 ?? '',
});
Step 2: Configure the Cognito User Pool in cdk/lib/cdk-stack.ts
Paste the following code into cdk/lib/cdk-stack.ts
.
Key points:
- Include
OAuthScope.COGNITO_ADMIN
in theUserPoolClient.oAuth.scopes
(line 55) for fetching user attributes. For more information, see the official documentation. - Securely retrieve Slack credentials from AWS Secrets Manager. (line 65-69)
- Ensure Slack credentials are injected without exposing them in the code. (line 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'),
},
});
}
}
Step 3: Deploy the Resources
Before deploying, replace the placeholder environment variables with actual values:
- SLACK_SECRET_ARN: ARN of the Secrets Manager entry containing Slack credentials.
- COGNITO_DOMAIN_PREFIX: A unique prefix for the Cognito domain.
Run the following commands to deploy:
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
Your Cognito User Pool is now ready and configured to work with Slack as an OIDC provider.
Configuring Slack App OAuth
To finalize the integration between Slack and Cognito, configure your Slack app’s OAuth settings.
Step 1: Configure Redirect URL
Navigate to the OAuth & Permissions page in your Slack app settings. Add the Cognito redirect URL:
- Press the
Add New Redirect URL
button. - Enter the following URL, replacing
<COGNITO_DOMAIN_PREFIX>
with your actual domain prefix:
https://<COGNITO_DOMAIN_PREFIX>.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse
- Press the
Save URLs
button.
Register your user pool domain URL with the
/oauth2/idpresponse
endpoint with your OIDC IdP.
If you’re unsure of your Cognito domain, locate it in the App Integration tab of the Cognito User Pool:
Step 2: Add OAuth Scopes
In the OAuth & Permissions page, add the required scope:
- Under User Token Scopes, add
users:read
. - Save your changes.
Step 3: Install the Slack App
- Click the
Install to <WORKSPACE>
button. - Follow the prompts to complete the installation.
Testing Sign in with Slack
Verify that the integration works by testing the “Sign in with Slack” flow:
Step 1: Access the Cognito Hosted UI
- Go to the Cognito App Client page.
- Press the
View Hosted UI
button.
Step 2: Initiate Slack Login
- On the Hosted UI, press the
Slack
button.
- Enter your workspace name and press
Continue
.
- Complete the Slack login process and press
Allow
to grant access.
Step 3: Verify the Callback URL
After successful login, you will be redirected to the callback URL configured in your Cognito app client. For this guide, the default is https://example.com/
.
Step 4: Confirm Federated User in Cognito
Run the following command to confirm that the federated user is added to your Cognito User Pool:
aws cognito-idp list-users \
--user-pool-id <COGNITO_USER_POOL_ID>
Sample output:
{
"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"
}
]
}
Your “Sign in with Slack” integration is now fully configured and operational.
Building React/Next.js App
This section guides you through configuring your React/Next.js application for authentication with Cognito and Slack.
Dot Env Configuration
Create an .env.local
file in the root directory of your Next.js app (./my-app/.env.local
) and add the following values:
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
(line 3) does not start with https://
.
Replace the placeholders (<COGNITO_USER_POOL_ID>
, <COGNITO_USER_POOL_CLIENT_ID>
, <COGNITO_DOMAIN_PREFIX>
) with the actual values from your Cognito setup.
Creating Auth Helper
To manage authentication, create a file named auth.ts
in the src
directory of your app (./my-app/src/auth.ts
) and implement the following functions:
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();
}
Key Points:
- Scopes:
- The
oauth.scopes
must includeaws.cognito.signin.user.admin
(line 27) to enable thefetchUserAttributes
function. - For more details, refer to the Cognito Resource Servers Documentation.
- The
- Redirect URLs:
- Ensure
oauth.redirectSignIn
andoauth.redirectSignOut
values (lines 31-32) exactly match the URLs configured in your Cognito app client, including trailing slashes.
- Ensure
Home Component
To display user information and handle sign-in and sign-out actions, implement the Home
component. Create the ./my-app/src/app/page.tsx
file with the following code:
'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>
);
}
Key Features:
- Authentication Handling (lines 20-25):
- Checks for valid session tokens.
- Initiates sign-in if no session is found.
- Fetches user attributes like email and username.
- Sign-Out Functionality (lines 49-55):
- Includes a button to handle sign-out actions.
- User Information (lines 33-46):
- Displays user information in a structured format using Tailwind CSS.
Testing Next.js App
To test the app, start the local development server:
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
- Open your browser and navigate to
http://localhost:3000
. - You should be redirected to the Slack sign-in page.
- Complete the sign-in process.
- Upon successful sign-in, you will see the app displaying user details.
Conclusion
The “Sign in with Slack” feature is a powerful tool for improving user experience (UX) in enterprise applications. By integrating Slack with Amazon Cognito, you offload complex authentication logic and focus on building your application’s core functionality.
Key Benefits:
- Seamless User Experience: Users can authenticate using their Slack accounts.
- Scalability: Cognito handles authentication workflows at scale.
- Enhanced Security: Utilize AWS’s secure infrastructure for identity management.
For authentication with Azure Entra ID, explore the following post.
Happy Coding! 🚀