Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Next.js Protect Routes Which Use Static Site Generation

In Next.js you have the option of server side rendering (SSR) or static site generation (SSG). Throughout the Next.js docs and community, SSG is recommended over SSR for performance reasons.

I have a Next.js build that uses SSG throughout the application, using getStaticProps() etc. to generate the content/pages at build time by integrating with an external CMS (Prismic). I prefer this because as mentioned it gives a performance boost and also most of the codebase can then use the same data-fetching strategy (at build time).

However, some of these pages need to be protected - meaning they should only be accessed by authenticated users. We are using Auth0 to generate a JWT token and have a React context provider save the status of the user (logged in or not) after validating the token in an API call.

But, I am struck that I don't seem to have a good way to protect SSG pages with this token. The recommended way here strikes me as odd because as far as I can tell this is a client-side redirect that could be manipulated by the client (for example - the client could manipulate it's local state/context or else tamper with whatever is returned from notloggedincondition) to show the static content or otherwise short-circuit the redirect.

For reference, here is a paste of that code:

import {useEffect} from 'react'
import {useRouter} from 'next/router'
export async function getStaticProps() {
  return {
    props: {
      hello: 'Hello World'
    }
  }
}

export default (props) => {
  const router = useRouter()
  useEffect(() => {
    if(notloggedincondition) {
      router.push('/login')
    }
  }, [])

  return <h1>Rest of the page</h1>
}

Note the <h1>Rest of the page</h1> could still be accessed by manipulating the client... so I want to secure the SSG at the request/response level and do a server side redirect (if need be), or something like that.

Short of something like this, is there no way to securely protect a SSG page without having to rely on client-side routing? Do I need to SSR the content even though it is no different really from the rest of the content, save for the requirement that only authenticated users can see it?

Perhaps I am missing something obvious, but it seems to me that even with a static site there should be a way to protect it without relying on client side routing. That is to say, it does not seem intrinsic to the concept of a statically generated site that every page must be public, so I'm wondering about a way to do this in Next.js that is secure.

like image 390
Thor Avatar asked Dec 19 '20 01:12

Thor


1 Answers

The best way I could find to accomplish this is via SWR fetches, statically generating a skeleton of the page with initial unprotected static data and then hydrating it with the refresh, if the refresh returns content.

This does require that you move logic gathering data for the protected page behind an API or CMS (anything which would clear your view of permissions), and converting existing routes to use API calls isn't a trivial task, so YMMV.

Important note: your redirect would still need to be client-side, but you can avoid having anything protected shown to an unauthorized user as that'd still be controlled at the server level. Since your biggest concern appears to be a user actively trying to compromise content by manipulating code, this seems to meet your risk remediation criteria (they would still be unable to access protected content).

Example page code:

import {useEffect} from 'react'
import {useRouter} from 'next/router'
import useSWR from 'swr'

export async function getStaticProps() {
  return {
    props: {
      hello: 'Hello World'
    }
  }
}

export default (props) => {
  const router = useRouter()

  // Access the protected content via an API route, 
  // provide the initial unprotected static content via the initialData param
  const { data } = useSWR('/api/protected-content', fetcher, { initialData: props })

  useEffect(() => {
    if(notloggedincondition) {
      router.push('/login')
    }
  }, [])

  return <h1>{ data.hello }</h1>
}

Then, an example API implementation at pages/api/protected-content:

export default async function ProtectedContent(req, res) {
  // Get a session object based on request cookies
  let session = await initUserSession(req, res);

  // If a session is available, return the protected content
  if (session.props.userSession) {
    return res.status(200).json({hello: 'This is protected content'});
  } else {
    return res.status(401).send("UNAUTHENTICATED");
  }
}
like image 90
bsplosion Avatar answered Sep 23 '22 18:09

bsplosion