NextAuth with AWS Cognito Email & Google Sign in
Tags: nextauth, cognito, nextjs, aws
Published: 2024-01-26 16:41:19

Hello, everyone 👋

This document provide information on how to use the Next Auth library to implement email and Google sign in with AWS Cognito.

Sample code: https://github.com/dango0812/nextauth-cognito

Understand content and code written in this document, it is helpful to be familiar with the following.

1. AWS Cognito is Service Provider
AWS Cognito does not offer direct support for Single Sign-On (SSO) using SAML 2.0 authentication methods. Therefore, if you intend to implement SSO with SAML 2.0, you must use a service such as Microsoft Azure or Okta (Auth0).

2. The AWS Cognito Provider in NextAuth provide code written using the OpenID Connect authentication method.

NextAuth Cognito Provider Code
```
export default function Cognito<P extends CognitoProfile>(
  options: OAuthUserConfig<P>
): OAuthConfig<P> {
  return {
    id: "cognito",
    name: "Cognito",
    wellKnown: `${options.issuer}/.well-known/openid-configuration`,
    idToken: true,
    ...
  }
}
```

You can see that need to input the well-known (openid-configuration) URL related to OpenID Connect.

To confirm more thoroughly, shall we check the internals of the OAuthConfig?

NextAuth OAuthConfig Code
```
export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
  wellKnown?: string
  authorization?: string | AuthorizationEndpointHandler
  token?: string | TokenEndpointHandler
  type: "oauth"
  checks?: ChecksType | ChecksType[]
  clientId?: string
  idToken?: boolean
  ...
}
```

type is oauth and use checks type used in OAuth

If you understand OpenID Connect, you can ascertain that it supports only OAuth functionality.

If you've understood the above content, go ahead and proceed with Google Sign-In using the CognitoProvider written below.

```
import CognitoProvider from "next-auth/providers/cognito"

CognitoProvider({
    id: 'cognito',
    name: 'Cognito',
    type: 'oauth',
    idToken: true,
    clientId: '',
    // your aws cognito client id
    clientSecret: null,
    // if your AWS Cognito secret exists, input your secret.
    // if the AWS Cognito secret is blank, input null.
    client: {
        token_endpoint_auth_method: "none"
    },
    checks: ['state', 'nonce'],
    issuer: '',
    // your aws cognito issuer
    wellKnown: 'https://cognito-idp.ap-northeast-2.amazonaws.com/####/.well-known/openid-configuration',
    // input your aws cognito userpool id in ####
    authorization: {
        url: '####/oauth2/authorize',
        // input your aws cognito domain url in ####
        params: {
            response_type: "code",
            client_id: '',
            // your aws cognito client Id
            identity_provider: 'Google',
            scope: 'openid email profile aws.cognito.signin.user.admin',
            redirect_uri: '####/api/auth/callback/cognito'
            // your redirect url
        },
    },
    profile(profile, tokens) {
        return { ...profile, ...tokens }
    }
}),
```

The redirect_uri should be set to api/auth/callback/cognito. 

NextAuth handle federated token and user api for AWS Cognito OAuth 2.0.


**If that's the case, how should approach email login?**

To implement the SAML 2.0 authentication method, you can either access it through the IdP URL provided by another platform or handle the authentication using NextAuth credentials provider with the AWS Cognito library.

```
CredentialsProvider({
    id: 'credentials',
    name: 'credentials',
    credentials: {
        email: { label: 'email', type: 'text' },
        password: { label: 'password', type: 'password' }
    },
    authorize(credentials) {
       
        const cognitoUser = new CognitoUser({
            Username: credentials?.email,
            Pool: "",
            // your aws cognito userpool
        });
    
        const authenticationDetails = new AuthenticationDetails({
            Username: credentials?.email,
            Password: credentials?.password
        });
        
        return new Promise((resolve, reject) => {
            cognitoUser.authenticateUser(authenticationDetails, {
                onSuccess: (session) => {
                    if (session instanceof CognitoUserSession) {
                        const userInfo = {
                            id: session.getIdToken().payload.sub,
                            email: session.getIdToken().payload.email,
                            name: session.getIdToken().payload.name,
                            idToken: session.getIdToken().getJwtToken(),
                            accessToken: session.getAccessToken().getJwtToken(),
                            refreshToken: session.getRefreshToken().getToken(),
                        };
    
                        resolve(userInfo);
                    }
                },
                onFailure: (error) => {
                    if (error) {
                        reject(error);
                    }
                }
            })
        })
    },
})
```

Thank you for reading it 😁

Sample Code: 
https://github.com/dango0812/nextauth-cognito

AWS Cognito Service Provider: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html

Next Auth CognitoProvider Internal Library: https://github.com/nextauthjs/next-auth/blob/v4/packages/next-auth/src/providers/cognito.ts