3

I have a session context for my NextJS application where anyone accessing /app/ directory pages have to go through an authorization check prior to allowing the user to access the page.

While my logic works in redirecting users without proper authentication, it is a bit glitchy because when someone navigate to the URL, /app/profile/ the page briefly loads before being redirected by Router.

I am wondering what is the best way to have this check happen prior to router loading the unauthorized page and redirecting them to the /login/ page.

Here are the steps in the authorization check:

  1. Check is the user object has a property, authorized
  2. Query the server for a session token
  3. if the object from the server request comes back with authorized = false, then redirect user to /login/

Here is the code:

import React, { createContext, useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import axios from 'axios'

export const SessionContext = createContext(null);

const AppSession = ({ children }) => {
    const router = useRouter()
    const routerPath = router.pathname;
    const [user, setUser] = useState({ user_id: '', user_email: '', user_avatar: ''})

    useEffect(()=> {
        // Check for populated user state if pages are accessed with the path /app/
        if (routerPath.includes("/app/")){
            if (user){
                if(user.authenticated === undefined){
                    // Check if user session exists
                    axios.get('/api/auth/session/')
                    .then(res => {
                        const data = res.data;
                        // Update user state depending on the data returned
                        setUser(data)
                        // If user session does not exist, redirect to /login/
                        if (data.authenticated === false){
                            router.push('/login/')
                        }
                    })
                    .catch(err => {
                        console.log(err)
                    });
                }
            }
        }
    }, [])

    return (
        <SessionContext.Provider value={{user, setUser}}>
            {children}
        </SessionContext.Provider>
    )

}

export const getUserState = () => {
    const { user } = useContext(SessionContext)
    return user;
}

export const updateUserState = () => {
    const { setUser } = useContext(SessionContext)
    return (user) => {
        setUser(user);
    }
}

export default AppSession;
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
cphill
  • 5,596
  • 16
  • 89
  • 182
  • Does this answer your question? [Next.js: How to prevent flash of the Unauthorized route/page prior to redirect when implementing a private route?](https://stackoverflow.com/questions/70297964/next-js-how-to-prevent-flash-of-the-unauthorized-route-page-prior-to-redirect-w) – Youssouf Oumar Aug 24 '22 at 14:48
  • Since you're checking the user's authenticated state on the client-side you can't prevent showing _something_ to the user (that could however be a loading page or blank page instead). If you want to prevent the page loading at all then you should have the check on the server-side. – juliomalves Aug 26 '22 at 18:28

2 Answers2

0

Since user.authenticated isn't defined in the initial user state you can conditionally render null or some loading indicator while user.authenticated is undefined. Once user.authenticated is defined the code should either redirect to "/login" or render the SessionContext.Provider component.

Example:

const AppSession = ({ children }) => {
  const router = useRouter();
  const routerPath = router.pathname;
  const [user, setUser] = useState({ user_id: '', user_email: '', user_avatar: ''});

  ...

  if (user.authenticated === undefined) {
    return null; // or loading indicator/spinner/etc
  }

  return (
    <SessionContext.Provider value={{ user, setUser }}>
      {children}
    </SessionContext.Provider>
  );
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks for your answer Drew, but I'm not sure I follow how this will check for the state before the page will load. If I used a loading indicator component, I could still see it do a brief flicker before the redirect to `/login/`. I'm trying to have the logic catch the object prior to router loading a page or component so it feels how server-side redirects prevent pages from loading. Is this best handled with SSR? – cphill Aug 24 '22 at 11:50
  • @cphill I'm not sure I follow what you mean. The page necessarily needs to load prior to being able to run any effects, i.e. like checking the auth status. This implementation blocks rendering the page content until the auth status is ***confirmed***. If you are seeing the page content prior to verifying the auth status then I suspect your initial state matches one of either your authenticated or unauthenticated "states" and the page content is rendered. What is the value of `user.authenticated` on the initial render cycle? – Drew Reese Aug 24 '22 at 15:51
  • @cphill And yes, maybe you could/should handle this logic on the server-side. Whether or not that's better is a bit subjective though. It's basically a determination you make for your codebase. – Drew Reese Aug 24 '22 at 15:52
0

Check out getServerSideProps, redirects in getServerSideProps and this article.

In your client-side, if you export the NextJS function definition named getServerSideProps from a page, NextJS pre-renders the page on each request using the data returned by getServerSideProps.

In other words, you can use getServerSideProps to retrieve and check the user while pre-rendering the page and then choose to redirect instead of render if your condition is not met.

Here is an example.

function Page({ data }) {
  // Render data...
}

export async function getServerSideProps(context) {
  const { req, res } = context;
  try {
    // get your user
    if (user.authenticated === undefined) {
      return {
        redirect: {
          permanent: false,
          destination: `/`,
        },
      };
    }

    
    return {
      props: {
        // any static props you want to deliver to the component
      },
    };
  } catch (e) {
    console.error("uh oh");
    return;
  }
}

Good luck!

Maker
  • 66
  • 5