Home > Enterprise >  NextJS redirect user back to the router came from after authentication
NextJS redirect user back to the router came from after authentication

Time:06-25

Hello I'm currently trying to redirect a user back to the page they originally clicked on after being authenticated but it's very tricky because of SSR and not having access to window.history object on the get getServerSideProps. Before the page even loads since the auth wrapper function kicks the user to auth page and never reaches the original page the user clicks on.

import { getEnvVar } from '@helpers/utils'
import { AUTH_TOKEN_KEY } from '@helpers/constants'

export const readDecodedToken = (token: string | null) => {
  if (!token) {
    return null
  }

  const base64Payload = token.split('.')[1]
  const payload = Buffer.from(base64Payload, 'base64')
  return JSON.parse(payload.toString())
}

export function checkAuthRedirect(req, env) {
  const { host } = req.headers
  console.error('host:', host) // eslint-disable-line no-console

  if (process.env.NEXT_PUBLIC_DANGEROUSLY_DISABLE_AUTH === 'true') {
    return false
  }

  // For SDLC production environment, only require auth for non-prod content environments
  const requireAuth =
    process.env.NEXT_PUBLIC_STAGE === 'production'
      ? ['draft1', 'qa1', 'staging1', 'green.production'].some((contentEnv) => host.includes(contentEnv))
      : true

  console.info(requireAuth)

  if (!requireAuth) {
    return false
  }

  let decodedToken

  try {
    decodedToken = readDecodedToken(req.cookies[AUTH_TOKEN_KEY])
  } catch (e) {
    // If cookie exists (but unable to decode), log error
    console.error(e)
  }

  // Token was properly decoded
  if (decodedToken) {
    return false
  }

  // Append env var to retrieve redirect URL
  const authRedirectUrlBase = 'https://idp.3rdpartpage.com/as/authorization.oauth2'
  const stage = process.env.NEXT_PUBLIC_STAGE as string
  const clientId = `website-webapp-${stage}-content-${env}`
  const redirectUri = `${process.env.NEXT_PUBLIC_FAPI_URL_BASE}/auth/web-${env}`

  // Cannot use URLSearchParams here, as escaped strings aren't properly parsed by MyID
  const authRedirectUrl = `${authRedirectUrlBase}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=openid profile`

  return authRedirectUrl
}

export function withRequireAuth(getServerSideProps) {
  const getServerSidePropsWithRequireAuth = async (params) => {
    const {
      req: {
        headers: { host },
      },
    } = params
    const env = getEnvVar(host)

    const authRedirect = checkAuthRedirect(params.req, env)

    const shouldCache = process.env.NEXT_PUBLIC_STAGE === 'production' && env === 'prod'

    if (shouldCache) {
      params.res.setHeader('Cache-Control', 'max-age=300')
    } else {
      params.res.setHeader('Cache-Control', 'no-store')
    }

    if (authRedirect) {
      return {
        redirect: {
          destination: authRedirect,
          permanent: false,
        },
      }
    }

    return getServerSideProps(params)
  }

  return getServerSidePropsWithRequireAuth
}

This is the /auth page that Nextjs automatically routes to

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import Cookies from 'js-cookie'
import { AUTH_TOKEN_KEY } from '@helpers/constants'
import { styled } from '@mui/material/styles'
import CircularProgress from '@mui/material/CircularProgress'
import Alert from '@mui/material/Alert'
import AlertTitle from '@mui/material/AlertTitle'

const AuthResultContainer = styled('div')`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
`

export default function Auth() {
  const router = useRouter()
  const { fapi_token: token, expires } = router.query

  const hasCredentials = token && expires

  useEffect(() => {
    if (hasCredentials) {
      Cookies.set(AUTH_TOKEN_KEY, token, {
        expires: new Date(Number(expires)),
      })
     // router.push('/')  this need to be removed and replaced with the actual route that user came from 
    }
  }, [token, expires, hasCredentials, router])

  const resultComponent = hasCredentials ? (
    <CircularProgress />
  ) : (
    <Alert severity="error">
      <AlertTitle>Authentication Error</AlertTitle>
      Unable to authenticate due to <strong>missing MyID auth credentials</strong>
    </Alert>
  )

  return <AuthResultContainer>{resultComponent}</AuthResultContainer>
}

On any page the user visit this is what the getServerSideProps looks like

export const getServerSideProps = withRequireAuth(async ({ req }) => {
  const { host } = req.headers
  const baseUrl = getHostUrl(host)
  const env = getEnvVar(host)

  const queryVariables = { env }
  const { data, error } = await queryFapi(HomepageQuery, queryVariables, req)
  const errorMessage = getApiErrorMessage(data, error)

  const homepageData = data?.data ?? null

  return {
    props: { homepageData, baseUrl, errorMessage, host },
  }
})

I have tried a few things to get it to work like replacing router.push('/') to window.history.go(-2) to go back two pages but when a user visits a route http://localhost:3000/watch it would kick itself out of the page.

I also tried story the original page in the history but since the withRequireAuth runs before even page load it doesn't even reach the original it just gets kicked to the auth page.

Any help and suggestion is greatly appreciated

CodePudding user response:

using 'cookies-next' help me solve the issue

export const getServerSideProps = ({ req, res }) => {
  const url = getCookies({ req, res }).redirectURL
  return { props: { url } }
}
  • Related