Source:  Twitter logo

I've been trying to implement a reliable authentication flow for a Next.js project but I'm completely lost now. I've already seen the examples repo of Next.js. But I have a lot of questions for a complete solution.

I have a express.js API and a separate Next.js frontend project. All the data and the authentication is handled by the API. Frontend just renders the pages with SSR. If I would just create a monolith project, where rendering the pages and all the data is handled by a single server (with a custom server option for Next.js I mean), I would just use express-session and csurf. It would be a traditional way to manage sessions and create security against CSRF.

Express.js API is not a requirement. It is just an example. It could be a Django API, or a .Net Core API. The main point is, it is a separate server and a separate project.

How can I have a simple, yet reliable structure? I've examined some of my favorite websites (netlify, zeit.co, heroku, spectrum.chat etc). Some of them use localstorage to store access and refresh tokens (XSS vulnerable). Some of them use cookies and they are not even HTTPOnly (both XSS and CSRF vulnerable). And examples like spectrum.chat use the way I mentioned above (cookie-session + preventing csrf).

I know there is the giant hype around the JWT tokens. But I find them too complex. Most of the tutorials just skips all the expiration, token refreshing, token revocation, blacklisting, whitelisting etc.

And many of the session cookie examples for Next.js almost never mention CSRF. Honestly, authentication is always a big problem for me. One day I read that HTTPOnly cookies should be used, next day I see a giant popular site not even using them. Or they say "never store your tokens to localStorage", and boom some giant project just uses this method.

Can anyone show me some direction for this situation?

Disclaimer: I am a maintainer of the free open source package below, but I think it's appropriate here as it's a common question there isn't a great answer for, as many of the popular solutions have the specific security flaws raised in the question (such as not using CSRF where appropriate and exposing Session Tokens or web tokens to client side JavaScript).

The package NextAuth.js attempts to address the issues raised above, with free open source software.

  • It uses httpOnly cookies with secure.
  • It has CSRF protection (double submit cookie method, with signed cookies).
  • Cookies are prefixed as appropriate (e.g. __HOST- or __Secure).
  • It supports email/passwordless signin and OAuth providers (with many included).
  • It supports both JSON Web Tokens (signed + encrypted) and Session Databases.
  • You can use it without a database (e.g. any ANSI SQL, MongoDB).
  • Has a live demo (view source).
  • It is 100% FOSS, it is not commercial software or a SaaS solution (is not selling anything).

Example API Route

e.g. page/api/auth/[...nextauth.js]

import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

const options = {
  providers: [
    // OAuth authentication providers
    Providers.Apple({
      clientId: process.env.APPLE_ID,
      clientSecret: process.env.APPLE_SECRET
    }),
    Providers.Google({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET
    }),
    // Sign in with email (passwordless)
    Providers.Email({
      server: process.env.MAIL_SERVER,
      from: '<no-reply@example.com>'
    }),
  ],
  // MySQL, Postgres or MongoDB database (or leave empty)
  database: process.env.DATABASE_URL
}

export default (req, res) => NextAuth(req, res, options)

Example React Component

e.g. pages/index.js

import React from 'react'
import { 
  useSession, 
  signin, 
  signout 
} from 'next-auth/client'

export default () => {
  const [ session, loading ] = useSession()

  return <p>
    {!session && <>
      Not signed in <br/>
      <button onClick={signin}>Sign in</button>
    </>}
    {session && <>
      Signed in as {session.user.email} <br/>
      <button onClick={signout}>Sign out</button>
    </>}
  </p>
}

Even if you don't choose to use it, you may find the code useful as a reference (e.g. how JSON Web Tokens are handled and how they are rotated in sessions.

11 users liked answer #0dislike answer #011
Iain Collins profile pic
Iain Collins

I've had to think about this as well for my current project. I use the same technologies: an ExpressJS API and a NextJS server-side-rendered front-end.

What I chose to do is use passport.js in the ExpressJS API. TheNetNinja on YouTube has a really good playlist of this with 21 episodes. He shows you how to implement Google OAuth 2.0 in your API, but this logic transfers to any other strategy (JWT, Email + Password, Facebook authentication etc.).

In the front-end, I would literally redirect the user to a url in the Express API. This url would show the user the Google OAuth screen, the user clicks on "Allow", the API does some more stuff, makes a cookie for the specific user and then redirects back to a url in the front end. Now, the user is authenticated.

About HTTPOnly cookies: I chose to turn off this feature, because I was storing information in the cookie that I needed in the front-end. If you have this feature enabled, then the front-end (javascript) doesn't have access to those cookies, because they are HTTPOnly.

Here's the link to the playlist I was talking about: https://www.youtube.com/watch?v=sakQbeRjgwg&list=PL4cUxeGkcC9jdm7QX143aMLAqyM-jTZ2x

Hope I've given you a direction you can take.

EDIT: I haven't answered your question about CSURF, but that's because I'm not familiar with it.

7 users liked answer #1dislike answer #17
Jonathan van de Groep profile pic
Jonathan van de Groep

I've finally found a solution!

Now I'm using csrf npm package, not csurf. csurf is just turns csrf into an express middleware.

So, I create a csrfSecret in the getInitialProps of _app. It creates the secret, sets it as a httpOnly cookie. Later, it creates a csrfToken and returns it with pageProps. So, I can access it with window.NEXT_DATA.props.csrfToken. If user refreshes the page, csrfSecret remains the same, but csrfToken gets renewed.

When I make a request to the proxied "/api/graphql API route, it first gets the csrf token from x-xsrf-token header and verifies it with the csrfSecret cookie value. After that, it extracts the value of authToken cookie and passes it to the actual GraphQL API.

API is all token based. It only needs a non-expiring access token. (BTW, It doesn't need to be JWT. Any cryptographically strong, random token can be used. Which means a reference/opaque token.)

CSRF check is not needed for the actual API, because it doesn't rely on cookies for authentication. It only checks authorization header. Both authToken and csrfSecret is httpOnly cookies. And I never even store them in client-side memory.

I think this is as secure as I could get. Now I'm happy with this solution.

3 users liked answer #2dislike answer #23
Onur Önder profile pic
Onur Önder

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).