Introduction#

GitHub this shows a simple auth flow with NextJS, Amplify and Cognito Hosted UI. Please note that the Cognito hosted UI redirect_url/code=abc, then this code is parsed and processed by the the Amplify js client, and tokens are saved in local storage or cookies.

  • Setup backend with Amplify CLI
  • Setup NextJS project
  • Create the auth flow
NextJS Amplify Cognito Hosted UI

Setup Backend#

init a new amplify project

amplify init

then add auth

amplify add auth

then choose a default auth flow then configure more details as here, please pay attention to configure the redirect signin URI and redirect signout URI

then push to aws, wait and copy aws-exports.js to the src in the nextjs project

amplify push

the aws-exports.js should look like below

const awsmobile = {
"aws_project_region": "xxx",
"aws_cognito_identity_pool_id": "xxx",
"aws_cognito_region": "xxx",
"aws_user_pools_id": "xxx",
"aws_user_pools_web_client_id": "xxx",
"oauth": {
"domain": "abc.auth.xxx.amazoncognito.com",
"scope": [
"phone",
"email",
"openid",
"profile",
"aws.cognito.signin.user.admin"
],
"redirectSignIn": "http://localhost:3000/",
"redirectSignOut": "http://localhost:3000/login/",
"responseType": "code"
},
"federationTarget": "COGNITO_USER_POOLS",
"aws_cognito_username_attributes": [
"EMAIL"
],
"aws_cognito_social_providers": [],
"aws_cognito_signup_attributes": [
"EMAIL"
],
"aws_cognito_mfa_configuration": "OFF",
"aws_cognito_mfa_types": [
"SMS"
],
"aws_cognito_password_protection_settings": {
"passwordPolicyMinLength": 8,
"passwordPolicyCharacters": []
},
"aws_cognito_verification_mechanisms": [
"EMAIL"
]
};

Create a NextJS App#

npx create-next-app@latest . --typescript

install chakra-ui and amplify sdk and some react dependencies

npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons @chakra-ui/icons aws-amplify

Configure App#

configure the app with AuthContext and ChakraProvider as below

import type { AppProps } from 'next/app'
import { ChakraProvider } from '@chakra-ui/react'
import { Amplify } from 'aws-amplify'
import config from './../src/aws-exports'
import { AuthContext } from '../src/auth'
import { useState } from 'react'
Amplify.configure({
...config,
ssr: true
})
export default function App({ Component, pageProps }: AppProps) {
const [user, setUser] = useState(null)
return (
<AuthContext.Provider value={{ user: user, setUser: setUser }}>
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
</AuthContext.Provider>
)
}

Configure Auth Flow#

create a very simple auth flow as below, please look at examples here and auth0

import { Box, Button, Heading } from '@chakra-ui/react'
import { Auth } from 'aws-amplify'
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import { AuthContext } from '../src/auth'
export default function Home() {
const router = useRouter()
const { user, setUser } = React.useContext(AuthContext)
useEffect(() => {
const getUser = async () => {
try {
const user = await Auth.currentAuthenticatedUser()
setUser(user)
} catch {
console.log('unauthenticated user')
router.push('/login')
}
}
getUser()
}, [setUser, router])
if (user) {
return (
<Box
bg={'orange.100'}
display={'flex'}
height={'100vh'}
margin={'auto'}
flexDirection={'column'}
justifyContent={'center'}
alignItems={'center'}
>
<Heading>Welcome Hai Tran</Heading>
<Button
colorScheme={'purple'}
minW={'200px'}
onClick={async () => {
await Auth.signOut()
}}
>
Sign Out
</Button>
</Box>
)
}
return null
}

The Login Page#

the key is to use Auth from Amplify for federation sigin with cognito identity provider here. The principle is simple

  • Identity providers can be Google, FaceBook, Github, Congit
  • Redirect to protected route after successfully auth
  • Configure cognito, auth0 to do the redirect routing
const user = await Auth.federatedSignIn({
provider: CognitoHostedUIIdentityProvider.Cognito
})
setUser(user)
import { CognitoHostedUIIdentityProvider } from '@aws-amplify/auth'
import { Box, Button, Input, Spacer, VStack } from '@chakra-ui/react'
import { Auth } from 'aws-amplify'
import React, { useState } from 'react'
import { AuthContext } from '../src/auth'
const LoginPage = () => {
const [name, setName] = useState('')
const [pass, setPass] = useState('')
const { user, setUser } = React.useContext(AuthContext)
return (
<Box
maxWidth={'2xl'}
margin={'auto'}
justifyContent={'center'}
alignItems={'center'}
display={'flex'}
flexDirection={'column'}
height={'100vh'}
>
<VStack
spacing={'20px'}
minW={'400px'}
bg={'gray.100'}
padding={'60px 20px 60px 20px'}
borderRadius={'5px'}
>
<Input
bg={'white'}
width={'100%'}
placeholder="email"
value={name}
onChange={event => {
setName(event.target.value)
}}
></Input>
<Input
// type={"password"}
bg={'white'}
placeholder="pass"
value={pass}
onChange={event => {
setPass(event.target.value)
}}
></Input>
<Spacer></Spacer>
<Button
colorScheme={'purple'}
width={'200px'}
padding={'10px'}
onClick={async () => {
console.log('log in using cognito hosted ui')
const user = await Auth.federatedSignIn({
provider: CognitoHostedUIIdentityProvider.Cognito
})
setUser(user)
}}
>
Sign In
</Button>
</VStack>
</Box>
)
}
export default LoginPage

Amplify Save Tokens#

const Home = () => {
const { user, setUser } = React.useContext(AuthContext)
const router = useRouter()
useEffect(() => {
const getUser = async () => {
try {
const user = await Auth.currentAuthenticatedUser()
setUser(user)
// console.log(user)
} catch (error) {
router.push('/login')
}
}
getUser()
}, [setUser, router])
if (user) {
return <SignOutForm></SignOutForm>
}
}
export default Home

so the line below will parse the auth code from redirect_url/code=abc, exchange the code for tokens, and save the tokens into local storage or cookies

const user = await Auth.currentAuthenticatedUser()