Search code examples
authenticationnext.jsbasic-authenticationnext-auth

Absolute simplest way to limit access to a Next.js route


I have two pages and two API routes in my Next.js project which I would like to limit access to. This is a very low-stakes project where complexity is a big downside, so my current solution is to put an unguessable string in the path to these pages. It's not this string, but this is the idea:

  • /pages/aabbccddeeff112233/page1
  • /pages/aabbccddeeff112233/page2
  • /pages/api/aabbccddeeff112233/route1
  • /pages/api/aabbccddeeff112233/route2

I would also, ultimately, like to be able to make the code public. Right now, of course, the "token" that I'm using to protect these routes is a folder in the git repo, so the repo needs to stay protected.

So I've been thinking about the simplest way (in LOC and setup) to deal with this. There is no db, there are max 2 users. OAuth2 would require setting up a GitHub provider and integrating NextAuth… I would somehow have to limit the accounts which can sign in to ones on an allowlist, but without checking them in. I'd have to set up some kind of login UI and unauthorized state for the protected pages. It just feels like an annoying amount of work for such a small project.

So my questions are:

  1. Is there some reason that my current approach is fundamentally insecure, assuming I'm able to keep the token hidden? Like, is the list of the app's routes shared on the client side?

  2. If I can keep the token-in-path approach, does anyone have an example of making this token configurable via an environment variable?

  3. I assume there's probably some reason that the route or env variable would be accessible via the debugger on the client side. Given that, what is the simplest way to protect routes on Next.js? Is there an equivalent to an .htaccess file? Super simple, but not stored in source control so code can still be shared?


Solution

  • To answer my questions:

    1. No, relying on an obscure path to keep pages hidden will not work in Next.js. If you visit a Next site and pop open the debugger, you will find a _next/static/_HASH/_buildManifest.js file in the Main Thread source tree. This lists all of the paths on the site. So your path won't require any brute forcing or guessing, just knowing the right place to look.

    2. See above

    3. The simplest actually-secure way to protect pages and API routes is fortunately very straightforward. All you need is an environment variable and some middleware. You create a middleware file and check for a token in the search params. (Note, accessing searchParams in middleware like this requires the very recent Next.js 13.4+, although it is possible with a bit more work on earlier versions as well.)

    /middleware.js

    import { NextResponse } from 'next/server';
    
    export async function middleware(req) {
      const supplied_token = req.nextUrl.searchParams.get('token')
      const valid_token = process.env.AUTH_TOKEN
    
      if (supplied_token !== valid_token) {
        const signinUrl = new URL('/', req.url)
        return NextResponse.redirect(signinUrl)
      }
    
      return NextResponse.next()
    }
    
    export const config = {
      matcher: ['/api/:path*', '/admin/:path*'],
    }
    

    Then you generate a token and add it to your .env.local and any cloud site build settings as AUTH_TOKEN. You will also need to modify any calls to API methods in your code to pass the token through:

    import { useSearchParams } from 'next/navigation'
    // ...
        const searchParams = useSearchParams()
        const token = searchParams.get('token')
        fetch(`/api/my_route?token=${token}`)
    

    And that's it! Now, when users stumble on a protected page, they are redirected to /. When you want to access a protected page, you just provide the token in the URL's query string:

    https://mysite.app/admin/delete_things?token=a1b2c3d4

    And although the paths to protected pages will appear in the manifest of built endpoints, the code of the pages won’t be transferred unless the token is provided.