diff --git a/docs/components/sidebar-content.tsx b/docs/components/sidebar-content.tsx
index fa8e56235a..843db28050 100644
--- a/docs/components/sidebar-content.tsx
+++ b/docs/components/sidebar-content.tsx
@@ -510,6 +510,25 @@ export const contents: Content[] = [
),
},
+ {
+ title: "Cognito",
+ href: "/docs/authentication/cognito",
+ isNew: true,
+ icon: () => (
+
+ ),
+ },
{
title: "Discord",
href: "/docs/authentication/discord",
diff --git a/docs/content/docs/authentication/cognito.mdx b/docs/content/docs/authentication/cognito.mdx
new file mode 100644
index 0000000000..46577f8c7a
--- /dev/null
+++ b/docs/content/docs/authentication/cognito.mdx
@@ -0,0 +1,78 @@
+---
+title: Cognito
+description: Amazon Cognito provider setup and usage.
+---
+
+
+
+ ### Get your Cognito Credentials
+ To integrate with Cognito, you need to set up a **User Pool** and an **App client** in the [Amazon Cognito Console](https://console.aws.amazon.com/cognito/).
+
+ Follow these steps:
+ 1. Go to the **Cognito Console** and create a **User Pool**.
+ 2. Under **App clients**, create a new **App client** (note the Client ID and Client Secret if enabled).
+ 3. Go to **Domain** and set a Cognito Hosted UI domain (e.g., `your-app.auth.us-east-1.amazoncognito.com`).
+ 4. In **App client settings**, enable:
+ - Allowed OAuth flows: `Authorization code grant`
+ - Allowed OAuth scopes: `openid`, `profile`, `email`
+ 5. Add your callback URL (e.g., `http://localhost:3000/api/auth/callback/cognito`).
+
+
+ - **User Pool is required** for Cognito authentication.
+ - Make sure the callback URL matches exactly what you configure in Cognito.
+
+
+
+
+ ### Configure the provider
+ Import the Cognito provider and pass it to the `providers` option of your `auth` instance.
+
+ ```ts title="auth.ts"
+ import { betterAuth } from "better-auth"
+
+ export const auth = betterAuth({
+ socialProvider: {
+ cognito({
+ clientId: process.env.COGNITO_CLIENT_ID as string, // [!code highlight]
+ clientSecret: process.env.COGNITO_CLIENT_SECRET as string, // [!code highlight]
+ domain: process.env.COGNITO_DOMAIN as string, // e.g. "your-app.auth.us-east-1.amazoncognito.com" [!code highlight]
+ region: process.env.COGNITO_REGION as string, // e.g. "us-east-1" [!code highlight]
+ userPoolId: process.env.COGNITO_USERPOOL_ID as string, // [!code highlight]
+ }),
+ },
+ })
+ ```
+
+
+
+ ### Sign In with Cognito
+ To sign in with Cognito, use the `signIn.social` function from the client.
+
+ ```ts title="auth-client.ts"
+ import { createAuthClient } from "better-auth/client"
+
+ const authClient = createAuthClient()
+
+ const signIn = async () => {
+ const data = await authClient.signIn.social({
+ provider: "cognito"
+ })
+ }
+ ```
+
+ ### Additional Options:
+ - `scope`: Additional OAuth2 scopes to request (combined with default permissions).
+ - Default: `"openid" "profile" "email"`
+ - Common Cognito scopes:
+ - `openid`: Required for OpenID Connect authentication
+ - `profile`: Access to basic profile info
+ - `email`: Access to user’s email
+ - `phone`: Access to user’s phone number
+ - `aws.cognito.signin.user.admin`: Grants access to Cognito-specific APIs
+ - Note: You must configure the scopes in your Cognito App Client settings. [available scopes](https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html#token-endpoint-userinfo)
+ - `getUserInfo`: Custom function to retrieve user information from the Cognito UserInfo endpoint.
+
+ For more information about Amazon Cognito's scopes and API capabilities, refer to the [official documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html?utm_source).
+
+
+
diff --git a/packages/better-auth/src/social-providers/cognito.ts b/packages/better-auth/src/social-providers/cognito.ts
new file mode 100644
index 0000000000..1292e341a0
--- /dev/null
+++ b/packages/better-auth/src/social-providers/cognito.ts
@@ -0,0 +1,265 @@
+import { betterFetch } from "@better-fetch/fetch";
+import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
+import { BetterAuthError } from "../error";
+import type { OAuthProvider, ProviderOptions } from "../oauth2";
+import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
+import { logger } from "../utils/logger";
+import { refreshAccessToken } from "../oauth2/refresh-access-token";
+import { APIError } from "better-call";
+
+export interface CognitoProfile {
+ sub: string;
+ email: string;
+ email_verified: boolean;
+ name: string;
+ given_name?: string;
+ family_name?: string;
+ picture?: string;
+ username?: string;
+ locale?: string;
+ phone_number?: string;
+ phone_number_verified?: boolean;
+ aud: string;
+ iss: string;
+ exp: number;
+ iat: number;
+ // Custom attributes from Cognito can be added here
+ [key: string]: any;
+}
+
+export interface CognitoOptions extends ProviderOptions {
+ /**
+ * The Cognito domain (e.g., "your-app.auth.us-east-1.amazoncognito.com")
+ */
+ domain: string;
+ /**
+ * AWS region where User Pool is hosted (e.g., "us-east-1")
+ */
+ region: string;
+ userPoolId: string;
+ requireClientSecret?: boolean;
+}
+
+export const cognito = (options: CognitoOptions) => {
+ if (!options.domain || !options.region || !options.userPoolId) {
+ logger.error(
+ "Domain, region and userPoolId are required for Amazon Cognito. Make sure to provide them in the options.",
+ );
+ throw new BetterAuthError("DOMAIN_AND_REGION_REQUIRED");
+ }
+
+ const cleanDomain = options.domain.replace(/^https?:\/\//, "");
+ const authorizationEndpoint = `https://${cleanDomain}/oauth2/authorize`;
+ const tokenEndpoint = `https://${cleanDomain}/oauth2/token`;
+ const userInfoEndpoint = `https://${cleanDomain}/oauth2/userinfo`;
+
+ return {
+ id: "cognito",
+ name: "Cognito",
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
+ if (!options.clientId) {
+ logger.error(
+ "ClientId is required for Amazon Cognito. Make sure to provide them in the options.",
+ );
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
+ }
+
+ if (options.requireClientSecret && !options.clientSecret) {
+ logger.error(
+ "Client Secret is required when requireClientSecret is true. Make sure to provide it in the options.",
+ );
+ throw new BetterAuthError("CLIENT_SECRET_REQUIRED");
+ }
+ const _scopes = options.disableDefaultScope
+ ? []
+ : ["openid", "profile", "email"];
+ options.scope && _scopes.push(...options.scope);
+ scopes && _scopes.push(...scopes);
+
+ const url = await createAuthorizationURL({
+ id: "cognito",
+ options: {
+ ...options,
+ },
+ authorizationEndpoint,
+ scopes: _scopes,
+ state,
+ codeVerifier,
+ redirectURI,
+ prompt: options.prompt,
+ });
+ return url;
+ },
+
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
+ return validateAuthorizationCode({
+ code,
+ codeVerifier,
+ redirectURI,
+ options,
+ tokenEndpoint,
+ });
+ },
+
+ refreshAccessToken: options.refreshAccessToken
+ ? options.refreshAccessToken
+ : async (refreshToken) => {
+ return refreshAccessToken({
+ refreshToken,
+ options: {
+ clientId: options.clientId,
+ clientKey: options.clientKey,
+ clientSecret: options.clientSecret,
+ },
+ tokenEndpoint,
+ });
+ },
+
+ async verifyIdToken(token, nonce) {
+ if (options.disableIdTokenSignIn) {
+ return false;
+ }
+ if (options.verifyIdToken) {
+ return options.verifyIdToken(token, nonce);
+ }
+
+ try {
+ const decodedHeader = decodeProtectedHeader(token);
+ const { kid, alg: jwtAlg } = decodedHeader;
+ if (!kid || !jwtAlg) return false;
+
+ const publicKey = await getCognitoPublicKey(
+ kid,
+ options.region,
+ options.userPoolId,
+ );
+ const expectedIssuer = `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`;
+
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
+ algorithms: [jwtAlg],
+ issuer: expectedIssuer,
+ audience: options.clientId,
+ maxTokenAge: "1h",
+ });
+
+ if (nonce && jwtClaims.nonce !== nonce) {
+ return false;
+ }
+ return true;
+ } catch (error) {
+ logger.error("Failed to verify ID token:", error);
+ return false;
+ }
+ },
+
+ async getUserInfo(token) {
+ if (options.getUserInfo) {
+ return options.getUserInfo(token);
+ }
+
+ if (token.idToken) {
+ try {
+ const profile = decodeJwt(token.idToken);
+ if (!profile) {
+ return null;
+ }
+ const name =
+ profile.name ||
+ profile.given_name ||
+ profile.username ||
+ profile.email;
+ const enrichedProfile = {
+ ...profile,
+ name,
+ };
+ const userMap = await options.mapProfileToUser?.(enrichedProfile);
+
+ return {
+ user: {
+ id: profile.sub,
+ name: enrichedProfile.name,
+ email: profile.email,
+ image: profile.picture,
+ emailVerified: profile.email_verified,
+ ...userMap,
+ },
+ data: enrichedProfile,
+ };
+ } catch (error) {
+ logger.error("Failed to decode ID token:", error);
+ }
+ }
+
+ if (token.accessToken) {
+ try {
+ const { data: userInfo } = await betterFetch(
+ userInfoEndpoint,
+ {
+ headers: {
+ Authorization: `Bearer ${token.accessToken}`,
+ },
+ },
+ );
+
+ if (userInfo) {
+ const userMap = await options.mapProfileToUser?.(userInfo);
+ return {
+ user: {
+ id: userInfo.sub,
+ name: userInfo.name || userInfo.given_name || userInfo.username,
+ email: userInfo.email,
+ image: userInfo.picture,
+ emailVerified: userInfo.email_verified,
+ ...userMap,
+ },
+ data: userInfo,
+ };
+ }
+ } catch (error) {
+ logger.error("Failed to fetch user info from Cognito:", error);
+ }
+ }
+
+ return null;
+ },
+
+ options,
+ } satisfies OAuthProvider;
+};
+
+export const getCognitoPublicKey = async (
+ kid: string,
+ region: string,
+ userPoolId: string,
+) => {
+ const COGNITO_JWKS_URI = `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;
+
+ try {
+ const { data } = await betterFetch<{
+ keys: Array<{
+ kid: string;
+ alg: string;
+ kty: string;
+ use: string;
+ n: string;
+ e: string;
+ }>;
+ }>(COGNITO_JWKS_URI);
+
+ if (!data?.keys) {
+ throw new APIError("BAD_REQUEST", {
+ message: "Keys not found",
+ });
+ }
+
+ const jwk = data.keys.find((key) => key.kid === kid);
+ if (!jwk) {
+ throw new Error(`JWK with kid ${kid} not found`);
+ }
+
+ return await importJWK(jwk, jwk.alg);
+ } catch (error) {
+ logger.error("Failed to fetch Cognito public key:", error);
+ throw error;
+ }
+};
diff --git a/packages/better-auth/src/social-providers/index.ts b/packages/better-auth/src/social-providers/index.ts
index 88898adbf0..e842985d9b 100644
--- a/packages/better-auth/src/social-providers/index.ts
+++ b/packages/better-auth/src/social-providers/index.ts
@@ -2,6 +2,7 @@ import * as z from "zod/v4";
import type { Prettify } from "../types/helper";
import { apple } from "./apple";
import { atlassian } from "./atlassian";
+import { cognito } from "./cognito";
import { discord } from "./discord";
import { facebook } from "./facebook";
import { figma } from "./figma";
@@ -33,6 +34,7 @@ import { paypal } from "./paypal";
export const socialProviders = {
apple,
atlassian,
+ cognito,
discord,
facebook,
figma,
@@ -83,6 +85,7 @@ export type SocialProviders = {
export * from "./apple";
export * from "./atlassian";
+export * from "./cognito";
export * from "./discord";
export * from "./dropbox";
export * from "./facebook";