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:
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?
If I can keep the token-in-path approach, does anyone have an example of making this token configurable via an environment variable?
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?
To answer my questions:
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.
See above
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.