简体   繁体   中英

aws-amplify-react and nextjs breaks my user context

I cant figure out why but when I use cognito with my own custom user context everything works just fine but as soon as I use withAuthenticator higher order component it breaks my user context and I cant for the life of me figure out why, or even how to fix it. Ill post my user context file below for reference and tell you where it breaks.

import { Auth } from 'aws-amplify'
import {createContext, useState, useEffect, useMemo} from 'react'

//TODO must redo cognito from scratch and will probably be able to keep this user context untouched

export const UserContext = createContext(null)

export const UserProvider = ({children}) => {
  
  const [ user, setUser ] = useState(null)
  const [ userEmail, setUserEmail ] = useState(null)
  const [ signInError, setSignInError ] = useState(false)

  useEffect(()=>{
    // AWS Cognito
    Auth.currentAuthenticatedUser().then(x=>setUser(x)).catch((err)=>setUser(null))
  },[])

   const handleSignInError = () => {
    console.log(signInError)
  }

  const login = (username, password) => {
    signInError && setSignInError(false)
    Auth.signIn(username, password)
    .then( x => {
      setUser(x)
      console.log('Welcome: ' + x.challengeParam.userAttributes.email)
      setUserEmail(x.challengeParam.userAttributes.email)
      setSignInError(false)
    })
    .catch((err)=>{
      console.log(err.code)
      if(err.code === 'UserNotFoundException' || 'NotAuthorizedException'){
        err.message = 'Invalid username or password'
        setSignInError(true)
        console.log(err.message)
      }
    })  
  }
  
  const logout = () => {
    Auth.signOut().then((x)=>{
      setUser(null)
      setUserEmail(null)
      return x
    })
  }

  const signup = (username, email, password) => {
    Auth.signUp({ username, password, attributes: { email } })
    .then( x => {
      setUser(x)
      return x
    })
    .catch((err)=>{
      if(err.code){
        err.message = 'Your Username or Password was incorrect'
      }
      throw err
    })
  }
  
  const vals = useMemo( () => ({user, login, logout, signup, handleSignInError, userEmail, signInError}), [user, userEmail, signInError])
  
  return(
    <UserContext.Provider value={vals}>
      {children}
    </UserContext.Provider>
  )
}

Under the login function it now returns user not found after I wrap a component and npm i aws-amplify-react . The funny thing is when I uninstall it I still get the same error and cant go back without fully removing amplify and going through a complete amplify init again. Even more confusing, My app is hosted on vercel and that breaks after I attempt to do this on my local machine. If im not missing something there and my app does break in the cloud even though I dont push my modified code then im guessing cognito is getting something in the cloud when I attempt this on my local machine and then screwing up my untouched copy on vercel????? Since then Ive also tried using next-auth which makes me think I should just stick to front end work or find a better solution? any help would be appreciated. Ill revert to my old setup and rebuild my cognito and amplify from scratch just to get it going again.

You need to call Cognito configure prior to calling your auth provider. Place it before you define your auth provider or context.

Auth.configure({...your_config})
const UserContext = () => {};

I also use a auth hook with my context that removes the need for a HOC.

import { useContext } from 'react';
export const useAuth = () => useContext(UserContext);

// use it in components and pages
const user = useAuth();

Ensure that your configuration is using all of the proper types. If you don't, it sometimes fails silently. For example ENV files are always passed as strings so some options must be cast to the proper type like cookie expires

{
  authenticationFlowType: 'USER_SRP_AUTH',
  cookieStorage: {
    ...other settings
    expires: Number(process.env.NEXT_PUBLIC_COGNITO_COOKIE_EXPIRES),
  }
};

You will also need to call Auth.configure on every page that you need access to Congito auth inside of getStaticPaths, getStaticProps, and getServerSideProps. This is because they are independently called from your app during build or on a server.

Auth.configure({...your_config})
const getStaticProps = () => {};
const getStaticPaths = () => {};
const getServerSideProps = () => {};

If you can use it, their hosted UI is pretty good.

Lastly, AWS has a few libraries for Amplify and I use @aws-amplify/auth - I don't know if this makes a difference.

I added the config file to my _app.js and set ssr: true for ssr authentication

import Amplify from 'aws-amplify'
import config from '../src/aws-exports'

Amplify.configure({...config, ssr: true})

Here is my working user context. I removed the signup function and will add it later once i work on it and test it.

import { Auth } from 'aws-amplify'
import {createContext, useState, useEffect, useMemo} from 'react'

export const UserContext = createContext(null)

export const UserProvider = ({children}) => {
  
  const [ user, setUser ] = useState(null)
  const [ userEmail, setUserEmail ] = useState(null)
  const [ signInError, setSignInError ] = useState(false)
  const [sub, setSub] = useState(null)
  useEffect(()=>{
    // AWS Cognito
    Auth.currentAuthenticatedUser()
      .then(x=>{
        setUser(x.username)
        setUserEmail(x.attributes.email)
        setSub(x.attributes.sub)
      })
      .catch((err)=>{
        console.log(err)
        setUser(null)
      })
  },[])

   const handleSignInError = () => {
    console.log(signInError)
  }

  const login = (username, password) => {
    signInError && setSignInError(false);
    Auth.signIn(username, password)
      .then((x) => {
        setUser(x.username)
        setSignInError(false)
        console.log(x)
      })
      .catch((err)=>{
        console.log(err)    
        setSignInError(true)
      })  
  }
  
  const logout = () => {
    Auth.signOut().then((x)=>{
      setUser(null)
      setUserEmail(null)
      setSub(null)
    })
  }

  
  }
  
  const vals = useMemo( () => ({user, sub, login, logout, handleSignInError, userEmail, signInError}), [user, userEmail, signInError, sub])
  
  return(
    <UserContext.Provider value={vals}>
      {children}
    </UserContext.Provider>
  )
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM