I have a problem regarding a custom auth wrapper.
# support-questions-legacy
f
I have a problem regarding a custom auth wrapper.
r
Can you describe the problem?
f
Yes sure...
So the problem is I'm currently trying to implement the solution proposed by @porcellus (Creating a custom AuthWrapper). The thing is I cannot quite get it to work, since it always crashes my app (infinite api calls etc.). Right now I am quite unsure about how to implement it or if its even the right way to go (since this issue #560 only appeared after a new supertokens version and was working before)
I did this.
Copy code
import { useRouter } from 'next/router'
import { ReactElement, useEffect, useState } from 'react'
import { useSessionContext } from 'supertokens-auth-react/recipe/session'
import { isEmailVerified as tpIsEmailVerified } from 'supertokens-auth-react/recipe/thirdpartyemailpassword'

const AuthWrapper = ({ children }: { children: ReactElement }) => {
  const sessionContext = useSessionContext()
  const router = useRouter()
  const [verified, setVerified] = useState<boolean>(false)

  useEffect(() => {
    console.log('running useEffect')

    const redirectToPath = new URL(window.location.href).searchParams.get(
      'redirectToPath'
    )

    async function isEmailVerified() {
      const { isVerified } = await tpIsEmailVerified()
      setVerified(isVerified)
    }

    isEmailVerified()

    if (sessionContext.loading === false) {
      if (sessionContext.doesSessionExist) {
        const atp = sessionContext.accessTokenPayload
        const { isUsernameSet } = atp

        if (isUsernameSet === false) {
          router.replace({
            pathname: '/auth/set-username',
            query: { redirectToPath },
          })
        }

        if (verified === false) {
          router.replace({
            pathname: '/auth/verify-email',
            query: { redirectToPath },
          })
        }

        if (
          router.asPath.includes('/auth') &&
          verified === true &&
          isUsernameSet === true
        ) {
          router.replace(redirectToPath || '/')
        }
      } else {
        router.replace({
          pathname: '/auth/signin',
          query: { redirectToPath },
        })
      }
    }
  }, [router, sessionContext, verified])

  if (sessionContext.loading === true) {
    return null
  }
  return children
}

export default AuthWrapper
r
I guess @porcellus can help out then
f
Alright, so I will wait for @porcellus then.
p
I'll check in a sec πŸ™‚
f
Thanks! Appreciate it
p
hmm, does it work if you comment out
isEmailVerified
(or
setVerified
)?
f
nope, infinite loop
The thing is: The issue with Google and Facebook (https://github.com/supertokens/supertokens-auth-react/issues/560) First appeared after this breaking change where the new SupertokensWrapper was introduced.
p
so since we introduced
loading
?
f
yes
before that my implementation was working just fine...
p
one issue I now see in this snippet is that you are calling
isEmailVerified
before the session is loaded. Also, the loading state is mostly for SSR support and anything you wrap with
<SessionAuth requireAuth={true>
should not be affected by it at all.
how did you update your implementation after the change? just checking
loading
in your components?
f
Yes, exactly
I moved the
isEmailVerified
below the
if(sessionContext.doesSessionExist)
and it's still an infinite loop
p
which do you end up on? can you test which branch keeps redirecting you?
f
nono there is no redirection going on
I just have the
useEffect
hook run infinite
and my curser is blinking πŸ˜„
p
hmm, so what page are you on? also, the react dev chrome extension thingy can tell you why a component was re-rendered
or there is a package called why-did-you-render, that also works.
f
I tested it on several pagese
first on the verify-email route
now just on auth/signin
p
oh, and you stay on the same page, but it keeps getting rerendered?
I mean if this is wrapping the
/auth
route, and a session doesn't exists, then this
useEffect
will keep calling
router.replace
can you comment out the redirections and replace them with console logs?
f
Yes, just found out about somthing. So basically the thing was redirecting all the time since no session was present.
let me fix that first
one moment
p
that's true for all the other branches
f
yes.
alright so wait a minute
I am fixing the other routes as well
Okay
so this is my new implementation:
Copy code
const AuthWrapper = ({ children }: { children: ReactElement }) => {
  const sessionContext = useSessionContext()
  const router = useRouter()

  useEffect(() => {
    console.log('running useEffect')

    const redirectToPath = new URL(window.location.href).searchParams.get(
      'redirectToPath'
    )

    if (sessionContext.loading === false) {
      if (sessionContext.doesSessionExist) {
        const atp = sessionContext.accessTokenPayload
        const { isUsernameSet, isEmailVerified } = atp

        if (isUsernameSet === false) {
          console.log('Username is not set')
          if (!router.asPath.includes('/auth/set-username')) {
            router.replace({
              pathname: '/auth/set-username',
              query: { redirectToPath },
            })
          }
        }

        if (isEmailVerified === false) {
          if (!router.asPath.includes('/auth/verify-email')) {
            router.replace({
              pathname: '/auth/verify-email',
              query: { redirectToPath },
            })
          }
        }
        if (
          router.asPath.includes('/auth') &&
          isEmailVerified === true &&
          isUsernameSet === true
        ) {
          router.replace(redirectToPath || '/')
        }
      } else if (!router.asPath.includes('/auth')) {
        router.replace({
          pathname: '/auth/signin',
          query: { redirectToPath },
        })
      }
    }
  }, [router, sessionContext]

  if (sessionContext.loading === true) {
    return null
  }
  return children
}
export default AuthWrapper
The email part only works because I save the thing in the accesstokenpayload on session creation ....
p
the email part? you mean getting
isEmailVerified
from the access token?
f
However I get flashes of my application
yes
p
well you redirect to
set-username
and then redirect to
auth/verify-email
flashes? so the protected page pops in before redirection?
f
yes.
Oh no, actually I didn't do it in the backend...
just tested it with facebook
google*
where the email is already verified on session creation. and i set the accesstokenpayload on session creation
however, if i use any other signup method I cannot update the atp since I don't have access to the session...
so this implementation is not going to work...
p
I'm a bit unsure, didn't you override
createNewSession
?
f
I am using supertokens-auth-react on the frontend. and supertokens-golang on the backend.
I thought it would be smart to store the isEmailVerfied in the atp.
however, this is impossible since I do not have access to the session when I override the VerifyEmailPost route like this:
Copy code
Override: &tpepmodels.OverrideStruct{
                    EmailVerificationFeature: &evmodels.OverrideStruct{
                        APIs: func(originalImplementation evmodels.APIInterface) evmodels.APIInterface {
                            originalVerifyEmailPOST := *originalImplementation.VerifyEmailPOST

                            (*originalImplementation.VerifyEmailPOST) = func(token string, options evmodels.APIOptions, userContext supertokens.UserContext) (evmodels.VerifyEmailPOSTResponse, error) {
                                resp, err := originalVerifyEmailPOST(token, options, userContext)

                                if err != nil {
                                    return evmodels.VerifyEmailPOSTResponse{}, err
                                }

                                if resp.OK != nil {
                                    // TODO: Update ATP
                                }

                                return resp, nil
                            }
                            return originalImplementation
                        },
                    },
                },
Which in someway makes sense.
So I need to use the classic isEmailVerfied feature...
in the frontend
p
I'm not a 100% familiar with golang (and our golang SDK), but I think you have access to both the request and response in the VerifyEmailPOST
so you could check for a session there (but make it optional) and update the session there.
also, I think we should focus on one issue at a time, and you could be using the normal isEmailVerified feature
meanwhile
f
You are right.
But I cannot use the normal implementation on the frontend since then my solution would look like this:
Which causes infinite redirects since
verified
has to be in the dependency array of
useEffect
p
well, you can have multiple `useEffects`: 1. you check the email verification and update it (and the dependency array only needs to have
[sessionContext.loading, sessionContext.doesSessionExist]
for that) 2. redirect in the other one
plus, you are not really using isVerified anywhere else so you don't necessarily need it as a state outside of the useEffect so that's another solution
f
My bad. It had nothing to do with dependency arrays...
p
also, the children popping in is an issue of
router.replace
taking effect after you render
children
. there are multiple solutions, the simplest (IMHO) is using an extra state variable (
showChildren
) that you can set to true after you run your checks for redirection.
f
yes, wait a minute.
will implement
Alright, that's working
However @rp_st could you help me with setting the isEmailVerified in the AccessTokenPayload=
?
r
@porcellus can help πŸ™‚
f
@rp_st yes he already did. My next question is how can I update the ATP in the backend when a email is verfied? This is a bit smoother I think.
Thank you very much!!!! @porcellus
r
You can call the updateAccessTokenPayload function
From the session object
And you can use the getSession function or verifySession function to get the session object
f
Copy code
Override: &tpepmodels.OverrideStruct{
                    EmailVerificationFeature: &evmodels.OverrideStruct{
                        APIs: func(originalImplementation evmodels.APIInterface) evmodels.APIInterface {
                            originalVerifyEmailPOST := *originalImplementation.VerifyEmailPOST

                            (*originalImplementation.VerifyEmailPOST) = func(token string, options evmodels.APIOptions, userContext supertokens.UserContext) (evmodels.VerifyEmailPOSTResponse, error) {
                                resp, err := originalVerifyEmailPOST(token, options, userContext)

                                if err != nil {
                                    return evmodels.VerifyEmailPOSTResponse{}, err
                                }

                                if resp.OK != nil {
                                    // TODO: Update ATP

                                }

                                return resp, nil
                            }
                            return originalImplementation
                        },
                    },
                },
This is what I have. The response has no attribute session.... so how would I do this?
p
also watch out for a few things: - make getting the session optional, since the user could click the link in a new browser. - also update isEmailVerified - on the frontend, you may want to check call isEmailVerified from time to time, since the user may verified their email address in a new browser, in which case you'd have to re-check and update it in some way.
r
You can get it by using the getSession function
f
Oh yes, I remember this...
Thank you @porcellus @rp_st for the amazing help...
r
✌️thank you @porcellus
2 Views