← Blog
Next.js: restrict pages to authenticated users
- authentication
- Javascript
- nextjs
- TypeScript
Many websites have pages that need to be restricted to authenticated users, for example profile pages or dashboards.
Next.js web framework comes with a lot of functionality built-in: JS compilation, rendering code on the server, and caching.
When it comes to most other aspects, it’s up to a programmer to build, including such needed functionality as restricting access to some pages only to authenticated users.
In this article we are going to build just that: a function that augments a page object, that could be used like that:
const ProfilePage: NextPage = () => {
return <PageLayout>{/* PAGE BODY */}</PageLayout>
}
// THE ACTUAL USAGE
requireAuth(ProfilePage)
export default ProfilePage
Here’s the implementation in TypeScript:
import { NextPage, NextPageContext } from "next"
import Router from "next/router"
const requireAuth = (page: NextPage) => {
// make sure this function is safe run several times
if (page.__authIsRequired) {
return
}
page.__authIsRequired = true
const originalGetInitialProps = page.getInitialProps
page.getInitialProps = async (ctx: NextPageContext) => {
const { res, req } = ctx
// httpClient on server side needs to be smart enough to send cookies
const fetchWithCookies = makeHttpClient(ctx)
const user = await fetchWithCookies("/api/users/current")
if (!user) {
if (res) {
const loginUrl = `/login?redirectTo=${encodeURIComponent(req.url)}`
res.writeHead(302, "Not authenticated", { Location: loginUrl })
res.end()
} else {
const loginUrl = `/login?redirectTo=${encodeURIComponent(window.location.pathname)}`
await Router.push(loginUrl)
}
return {}
}
return originalGetInitialProps ? originalGetInitialProps(ctx) : {}
}
}
export default requireAuth
In order to make it work on the server we are going to need one more utility function fetchWithCookies.
When making an HTTP request from inside a web browser, the browser automatically sends cookies with each request and then stores new cookies if a response comes in with a set-cookie header.
On server-side we need to build this feature ourselves, because neither Next.js nor NodeJS make any assumption about a request flow.
In order to do that we can leverage the ctx: { req, res } object that Next.js passes into getInitialProps during SSR.
// return an augmented `fetch` function that correctly sends
// and receives cookies during SSR
function makeHttpClient(ctx: NextPageContext): typeof fetch {
return async function fetchWithCookies(input: RequestInfo, options: RequestInit = {}) {
const actualOptions: RequestInit = {
credentials: "include", // send cookies with request
redirect: "manual", // don't follow redirects
...options,
headers: {
cookie: ctx?.req?.headers?.cookie ?? "",
...(options.headers ?? {}),
},
}
// on server side we use isomorphic-unfetch NPM package,
// on client side we use browser built-in `fetch` function
let isomorphicFetch: typeof fetch
if (typeof window === "undefined") {
isomorphicFetch = (await import("isomorphic-unfetch")).default
} else {
isomorphicFetch = window.fetch
}
const result = await isomorphicFetch(input, actualOptions)
// browsers (client-size) already handle this case automatically,
// but in case some cookies are getting set in response,
// we need to handle that use case on server-side as well
if (ctx?.res) {
const cookiesFromApi = result.headers.get("set-cookie")
if (cookiesFromApi) {
ctx?.res.setHeader("set-cookie", cookiesFromApi)
}
}
return result
}
}
Of course this implementation is only one way to do this, for example the HTTP client looks different if you are using Apollo Graphql.
Thank you for reading!