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
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
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
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.
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:
api route:
^ 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 request401s (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?