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
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 (<Boxbg={'orange.100'}display={'flex'}height={'100vh'}margin={'auto'}flexDirection={'column'}justifyContent={'center'}alignItems={'center'}><Heading>Welcome Hai Tran</Heading><ButtoncolorScheme={'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 (<BoxmaxWidth={'2xl'}margin={'auto'}justifyContent={'center'}alignItems={'center'}display={'flex'}flexDirection={'column'}height={'100vh'}><VStackspacing={'20px'}minW={'400px'}bg={'gray.100'}padding={'60px 20px 60px 20px'}borderRadius={'5px'}><Inputbg={'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><ButtoncolorScheme={'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()