Nextjs-auth0: Require authentication using getServerSideProps

Created on 5 Jun 2020  路  6Comments  路  Source: auth0/nextjs-auth0

Edit: to be clear, this is a request for an example, not an issue report.

With Next 9.4.4, it's recommended that instead of using getInitialProps, you should use getServerSideProps instead. The current example for requiring login on a page uses getInitialProps: https://github.com/auth0/nextjs-auth0/blob/master/examples/basic-example/components/with-auth.jsx

Describe the ideal solution

Could we please have an example of the best way to require authentication on a page - much the same as the withAuth example using a HOC and getInitialProps, but instead using getServerSideProps? I found this which suggests an approach, but I'm not clear it's quite the right way to go about it, and also my Typescript skills are ... limited and I could do with some assistance translating it to Javascript: https://github.com/vercel/next.js/discussions/10925#discussioncomment-12471

Alternatives and current work-arounds

It's fairly easy to write code to require the user be in session:

import auth0 from '../lib/auth0'

export default async getServerSideProps(context) {
  const session = await auth0.getSession(context.req)
  if (!session || !session.user) {
    context.res.writeHead(302, {
        Location: '/api/login'
      })
      context.res.end()
  } else {
    return {
      props: {}
   }
}

But it'd be good to see the recommended way of putting this in a HOC or similar rather than do it on every page, or just write it as a helper function. I'll have a go and post back if I can get it working, but it'd be good to see how other people are approaching this problem.

Edit 2: found this on how to do it, which is from the NextJS repo, but it's just what I've suggested above: https://github.com/vercel/next.js/blob/56633ed6bebf9e4a2bcb3feff2e3de3bd61d1da9/examples/auth0/pages/advanced/ssr-profile.js#L20-L35

Most helpful comment

I'm not sure if this is the right thread or not, but i've found that the session is MIA if i make a request from getServerSideProps. Am I missing something?

config:

export default initAuth0({
  domain,
  clientId,
  clientSecret,
  scope: 'openid profile',
  redirectUri: `${SITE_URL}/api/auth/callback`,
  postLogoutRedirectUri: SITE_URL,
  session: {
    cookieSecret,
    cookieLifetime: 60 * 60 * 8,
    storeIdToken: true,
    storeAccessToken: true,
    storeRefreshToken: true,
  },
})

api route:

import { getAllChannels } from '~/data/channels'
import auth from '~/utils/auth'

export default auth.requireAuthentication(
  async (req, res): Promise<void> => {
    const session = await auth.getSession(req)
    console.log('session', session?.idToken)
    const channels = await getAllChannels()

    res.end(JSON.stringify(channels))
  },
)

^ If I hit a page that calls that api route from inside the component, it works. The request succeeds, and I can see the id token in the logs. If I refresh the page (triggering getServerSideProps) the request 401s (even though, if I don't redirect the page and let the same request get kicked off by the browser, it succeeds).

Am I missing a step to make the session available in the initial request?

All 6 comments

I'm not sure if this is the right thread or not, but i've found that the session is MIA if i make a request from getServerSideProps. Am I missing something?

config:

export default initAuth0({
  domain,
  clientId,
  clientSecret,
  scope: 'openid profile',
  redirectUri: `${SITE_URL}/api/auth/callback`,
  postLogoutRedirectUri: SITE_URL,
  session: {
    cookieSecret,
    cookieLifetime: 60 * 60 * 8,
    storeIdToken: true,
    storeAccessToken: true,
    storeRefreshToken: true,
  },
})

api route:

import { getAllChannels } from '~/data/channels'
import auth from '~/utils/auth'

export default auth.requireAuthentication(
  async (req, res): Promise<void> => {
    const session = await auth.getSession(req)
    console.log('session', session?.idToken)
    const channels = await getAllChannels()

    res.end(JSON.stringify(channels))
  },
)

^ If I hit a page that calls that api route from inside the component, it works. The request succeeds, and I can see the id token in the logs. If I refresh the page (triggering getServerSideProps) the request 401s (even though, if I don't redirect the page and let the same request get kicked off by the browser, it succeeds).

Am I missing a step to make the session available in the initial request?

@drewwyatt Did you figure it out? I'm having the same issue

I am having the same issue.

+

I ended up writing my own helper for handling this and a couple typescript utils, hope it helps!

The helpers at utils/authenticated-server-side-props.ts:

import { GetServerSidePropsContext } from 'next'
import auth0 from './auth0'

type AsyncReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => Promise<infer U>
  ? U
  : T extends (...args: any) => infer U
  ? U
  : any

export type InferAuthenticatedServerSideProps<
  T extends (...args: any) => Promise<{ props: any }>
> = AsyncReturnType<T>['props']

type User = Record<string, unknown>

type DefaultAuthenticatedProps = {
  user: User
}

type EmptyProps = {
  props: Record<string, unknown>
}

function authenticatedServerSideProps<T extends EmptyProps = EmptyProps>(
  getServerSidePropsFunc?: (
    ctx: GetServerSidePropsContext,
    user: User,
  ) => Promise<T>,
) {
  return async function getAuthenticatedServerSideProps(
    ctx: GetServerSidePropsContext,
  ): Promise<{ props: T['props'] & DefaultAuthenticatedProps }> {
    const session = await auth0.getSession(ctx.req)
    if (!session || !session.user) {
      return ({
        redirect: {
          destination: '/api/login',
          permanent: false,
        },
        // We have to trick the TS compiler here.
      } as unknown) as { props: T['props'] & DefaultAuthenticatedProps }
    }

    const { user } = session

    if (getServerSidePropsFunc) {
      return {
        props: {
          user,
          ...((await getServerSidePropsFunc(ctx, user)).props || {}),
        },
      }
    }

    return {
      props: {
        user,
      },
    }
  }
}

export default authenticatedServerSideProps

Usage example without additional props at pages/example-simple.tsx:

import authenticatedServerSideProps, { InferAuthenticatedServerSideProps } from 'utils/authenticated-server-side-props'

export const getServerSideProps = authenticatedServerSideProps()

function ExampleSimple({ user }: InferAuthenticatedServerSideProps<typeof getServerSideProps>) {
  return (
    <h1>Hello {user.name}</h1>
  )
}

export default ExampleSimple

Usage example with a custom getServerSideProps at pages/example-advanced.tsx:

import authenticatedServerSideProps, { InferAuthenticatedServerSideProps } from 'utils/authenticated-server-side-props'
import db from 'utils/db'

export const getServerSideProps = authenticatedServerSideProps(async (_ctx, user) => {
  const posts = await db.posts.findMany({
    where: {
      userId: user.sub
    }
  })

  return {
    props: {
      posts
    }
  }
})

// Both user and posts are fully typed here
function ExampleAdvanced({ user, posts }: InferAuthenticatedServerSideProps<typeof getServerSideProps>) {
  return (
    <div>
      <h1>Hello {user.name}, these are your posts:</h1>
      <ul>
        {posts.map(post => <li id={post.id}>{post.title}</li>)}
      </ul>
    </div>
  )
}

export default ExampleAdvanced

Hi everyone, we've just released v1.0.0-beta.0 that includes support for protecting server-side rendered pages with getServerSideProps. Please take a look at the example.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pasenidis picture pasenidis  路  4Comments

shansmith01 picture shansmith01  路  4Comments

shicholas picture shicholas  路  5Comments

lunchboxav picture lunchboxav  路  5Comments

platocrat picture platocrat  路  3Comments