How to Integrate “Sign in with Slack” Using AWS Cognito User Pools

How to Integrate “Sign in with Slack” Using AWS Cognito User Pools

Takahiro Iwasa
Takahiro Iwasa
10 min read
Cognito OIDC

This note describes how to federate Cognito user pools with Slack using OIDC to implement “Sign in with Slack”.

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:

Terminal window
npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>

Initializing CDK Project

Create a directory for your CDK project and initialize it:

Terminal window
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:

Terminal window
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

Installing AWS Amplify

AWS Amplify simplifies the interaction with Cognito, allowing you to manage authentication with ease.

Install Amplify in your project directory:

Terminal window
cd my-app
npm i aws-amplify

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:

Open your Slack workspace menu and go to Manage apps.

Manage Apps

On the Slack app directory page, press the Build button.

Build New App

On your apps page, click the Create an App button.

Create App

When prompted, select the From an app manifest option. This simplifies the process of creating and configuring your Slack app.

https://api.slack.com/reference/manifests

Use the app manifest system to quickly create, configure, and reuse Slack app configurations.

App Manifest Option

Choose the workspace where your app will be installed.

Select Workspace

Provide a name for your app. In this note, we use Sign in with Slack. Leave the other fields as default.

Name Your App

Press the Create button to finalize the setup of your Slack app.

Finalize Creation

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:

Step 1. Retrieve Slack App Credentials

Navigate to your Slack app’s Basic Information page and copy the Client ID and Client Secret values.

These credentials will be used to configure the Cognito User Pool later.

Slack App Credentials

Step 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:

Important

Avoid hardcoding your Slack credentials in your AWS CDK files. Always use secure storage like AWS Secrets Manager.

Terminal window
aws secretsmanager create-secret \
--name sign-in-with-slack \
--secret-string '{"clientId": "<YOUR_CLIENT_ID>", "clientSecret": "<YOUR_CLIENT_SECRET>"}'

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:

cdk/bin/cdk.ts
#!/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.

  • Include OAuthScope.COGNITO_ADMIN in the UserPoolClient.oAuth.scopes (line 55) for fetching user attributes. Refer to the official documentation.
  • Securely retrieve Slack credentials from AWS Secrets Manager. (line 65-69)
  • Slack credentials are injected without exposing them in the code. (line 75-82)
cdk/lib/cdk-stack.ts
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 Stack

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:

Terminal window
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

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:

  1. Press the Add New Redirect URL button.
  2. 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
    Using OIDC identity providers with a user pool

    Register your user pool domain URL with the /oauth2/idpresponse endpoint with your OIDC IdP.

  3. Press the Save URLs button.

Add Redirect URL

If you’re unsure of your Cognito domain, locate it in the App Integration tab of the Cognito User Pool.

Cognito Domain

Step 2: Add OAuth Scopes

In the OAuth & Permissions page, add the required scope users:read under User Token Scopes. Save your changes to complete.

Add OAuth Scopes

Step 3: Install the Slack App

Click the Install to <WORKSPACE> button and follow the prompts to complete the installation.

Install Slack App Step 1

Install Slack App Step 2

Testing Sign in with Slack

Verify that the federation works by testing the “Sign in with Slack” flow.

Go to the Cognito App Client page and press the View Hosted UI button.

View Hosted UI

On the Hosted UI, press the Slack button.

Slack Button

Enter your workspace name and press Continue.

Enter Workspace

Complete the Slack login process and press Allow to grant access.

Allow Access

Allow Access

After successful login, you will be redirected to the callback URL configured in your Cognito app client. For this example, the default is https://example.com/.

Callback URL

Run the following command to confirm that the federated user is added to your Cognito User Pool:

Terminal window
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"
}
]
}

Building Frontend

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:

.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
🔥 Caution

Ensure the NEXT_PUBLIC_OAUTH_DOMAIN (line 3) does not start with https://.

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:

  • Scopes:
    • The oauth.scopes must include aws.cognito.signin.user.admin (line 27) to enable the fetchUserAttributes function. For more details, refer to the official documentation.
  • Redirect URLs:
    • Ensure oauth.redirectSignIn and oauth.redirectSignOut values (lines 31-32) exactly match the URLs configured in your Cognito app client, including trailing slashes.
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();
}

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:

  • 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.
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>
);
}

Testing Next.js App

To test the app, start the local development server:

Terminal window
npm run 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. Upon successful sign-in, you will see the app displaying user details.

App Page

Takahiro Iwasa

Takahiro Iwasa

Software Developer
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Japan AWS Top Engineers 2020-2023.