9

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 a httpOnly 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 using getServerSideProps 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.

juliomalves
  • 42,130
  • 20
  • 150
  • 146
aleksander frnczak
  • 399
  • 1
  • 2
  • 12

2 Answers2

37

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
})
juliomalves
  • 42,130
  • 20
  • 150
  • 146
  • Thanks 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 Feb 08 '21 at 16:32
  • i 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 Feb 08 '21 at 16:56
  • 1
    Okay, 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! – aleksander frnczak Feb 08 '21 at 21:40
  • The 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. – juliomalves Feb 08 '21 at 21:44
  • This seems to be a nice approach for dynamic pages, dunno how to make it work for static pages (using getStaticProps). Any thoughts? – carlosbvz Oct 23 '21 at 18:22
  • If 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. – juliomalves Oct 23 '21 at 18:28
  • @juliomalves can you add ts version? i'm unable to type `gssp`? i got `context: GetServerSidePropsContext` working well but don't know how to type `gssp`. i went with `any`. furthermore, i get an error saying `TypeError: Cannot read property 'user' of undefined`, how do i solve it? – deadcoder0904 Jan 16 '22 at 12:02
  • 1
    @deadcoder0904 Typing `gssp` as `requireAuthentication(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. – juliomalves Jan 17 '22 at 19:14
  • @juliomalves it was an error bcz i wasn't wrapping `ironSessionSsr` so when i did that, it worked but typing `gssp` as `GetServerSideProps` doesn't work for me, sadly :( – deadcoder0904 Jan 19 '22 at 05:27
  • 1
    @deadcoder0904 You may want to create a new question for the typing issue you're having. – juliomalves Jan 19 '22 at 07:55
  • 1
    @juliomalves i would've but it isn't a significant of a problem. using `any` for now :) – deadcoder0904 Jan 19 '22 at 13:37
  • is there a way to use this approach if I don't need to call getServerSideProps from the protected page? – gullerg Mar 03 '22 at 19:47
  • @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 https://stackoverflow.com/a/70659746/1870780. – juliomalves Mar 04 '22 at 13:30
  • The 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? – gullerg Mar 04 '22 at 19:58
  • @procul You don't have to add any other server-side fetching if you don't want to. You could remove the `gssp` param from `requireAuthentication(gssp)` and refactor the HOC code accordingly if you don't want that. You'll probably still have to return an empty `props` object from the HOC though. – juliomalves Mar 04 '22 at 21:24
  • 1
    That makes sense - thanks!! – gullerg Mar 04 '22 at 21:27
  • 1
    This is what I have been searching for 3 hours. Thanks! – agahpars May 13 '22 at 00:00
3

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 }
deadcoder0904
  • 7,232
  • 12
  • 66
  • 163