Create a HOC (higher order component) for authentication in Next.js
Solution 1
You should separate and extract your authentication logic from getServerSideProps
into a re-usable higher-order function.
For instance, you could have the following function that would accept another function (your getServerSideProps
), and would redirect to your login page if the userToken
isn't set.
export function requireAuthentication(gssp) {
return async (context) => {
const { req, res } = context;
const token = req.cookies.userToken;
if (!token) {
// Redirect to login page
return {
redirect: {
destination: '/admin/login',
statusCode: 302
}
};
}
return await gssp(context); // Continue on to call `getServerSideProps` logic
}
}
You would then use it in your page by wrapping the getServerSideProps
function.
// pages/index.js (or some other page)
export const getServerSideProps = requireAuthentication(context => {
// Your normal `getServerSideProps` code here
})
Solution 2
Based on Julio's answer, I made it work for iron-session
:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '@/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr
function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }
aleksander frnczak
Updated on July 22, 2022Comments
-
aleksander frnczak almost 2 years
So I'm creating authentication logic in my Next.js app. I created
/api/auth/login
page where I handle request and if user's data is good, I'm creating ahttpOnly
cookie with JWT token and returning some data to frontend. That part works fine but I need some way to protect some pages so only the logged users can access them and I have problem with creating a HOC for that.The best way I saw is to use
getInitialProps
but on Next.js site it says that I shouldn't use it anymore, so I thought about usinggetServerSideProps
but that doesn't work either or I'm probably doing something wrong.This is my HOC code: (cookie are stored under
userToken
name)import React from 'react'; const jwt = require('jsonwebtoken'); const RequireAuthentication = (WrappedComponent) => { return WrappedComponent; }; export async function getServerSideProps({req,res}) { const token = req.cookies.userToken || null; // no token so i take user to login page if (!token) { res.statusCode = 302; res.setHeader('Location', '/admin/login') return {props: {}} } else { // we have token so i return nothing without changing location return; } } export default RequireAuthentication;
If you have any other ideas how to handle auth in Next.js with cookies I would be grateful for help because I'm new to the server side rendering react/auth.
-
aleksander frnczak about 3 yearsThanks for the help! Although I have another problem, i can't get cookies in "getServerSideProps", i tried with req.cokies.userToken, with npm packages like nookies or cookie and nothing is working. I get the context object but when i access req.cookies a get and empty object event if i see that cookie on the browser
-
aleksander frnczak about 3 yearsi didn't get this issue before because i didn't get so far with my solution so i think it's related to this
-
aleksander frnczak about 3 yearsOkay, i even don't have the cookie object in my req.headers co i just start a new thread because im not sure if this is related to your answer, but thank you very much for help!
-
juliomalves about 3 yearsThe HOC will just pass what's sent to it so shouldn't be an issue in itself. I've tested it locally, and I can see cookies being passed in
req.headers.cookie
inside the HOC. -
carlosbvz over 2 yearsThis seems to be a nice approach for dynamic pages, dunno how to make it work for static pages (using getStaticProps). Any thoughts?
-
juliomalves over 2 yearsIf you're using static pages the authentication needs to happen on the client-side.
getStaticProps
runs at build time, and has no access to request-specific information like cookies. That being said, you could create some kind of custom hook (to be re-used) to deal with it on the client-side. -
deadcoder0904 over 2 years@juliomalves can you add ts version? i'm unable to type
gssp
? i gotcontext: GetServerSidePropsContext
working well but don't know how to typegssp
. i went withany
. furthermore, i get an error sayingTypeError: Cannot read property 'user' of undefined
, how do i solve it? -
juliomalves over 2 years@deadcoder0904 Typing
gssp
asrequireAuthentication(gssp: GetServerSideProps)
works for me. Regarding the error you're getting, you may want to create a new question for that as I don't think it's related to this question. -
deadcoder0904 over 2 years@juliomalves it was an error bcz i wasn't wrapping
ironSessionSsr
so when i did that, it worked but typinggssp
asGetServerSideProps
doesn't work for me, sadly :( -
juliomalves over 2 years@deadcoder0904 You may want to create a new question for the typing issue you're having.
-
deadcoder0904 over 2 years@juliomalves i would've but it isn't a significant of a problem. using
any
for now :) -
procul about 2 yearsis there a way to use this approach if I don't need to call getServerSideProps from the protected page?
-
juliomalves about 2 years@procul If you're not using
getServerSideProps
is the authentication happening on the client then? For that you can have a look at the client-side solution proposed in stackoverflow.com/a/70659746/1870780. -
procul about 2 yearsThe authentication is still happening on the the server-side; however, I don't need to add additional server-side data fetching for some of the protected pages. The approach above seems to be a higher-order function for the gssp for the page, which means that it can't really be used for a page unless it has some some additional server-side fethching? Or am I mistaken?
-
juliomalves about 2 years@procul You don't have to add any other server-side fetching if you don't want to. You could remove the
gssp
param fromrequireAuthentication(gssp)
and refactor the HOC code accordingly if you don't want that. You'll probably still have to return an emptyprops
object from the HOC though. -
procul about 2 yearsThat makes sense - thanks!!
-
agahpars almost 2 yearsThis is what I have been searching for 3 hours. Thanks!