Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nextjs route middleware for authentication

I'm trying to figure out an appropriate way of doing authentication, which I know is a touchy subject on the GitHub issue page.

My authentication is simple. I store a JWT token in the session. I send it to a different server for approval. If I get back true, we keep going, if I get back false, it clears the session and puts sends them to the main page.

In my server.js file I have the following (note- I am using the example from nextjs learn and just adding isAuthenticated):

function isAuthenticated(req, res, next) {
  //checks go here

  //if (req.user.authenticated)
   // return next();

  // IF A USER ISN'T LOGGED IN, THEN REDIRECT THEM SOMEWHERE
  res.redirect('/');
}

server.get('/p/:id', isAuthenticated, (req, res) => {
  const actualPage = '/post'
  const queryParams = { id: req.params.id }
  app.render(req, res, actualPage, queryParams)
})

This works as designed. If I refresh the page /p/123, it will redirect to the /. However, if I go there via a next/link href, it doesn't. Which I believe is because it's not using express at this point but next's custom routing.

Is there a way I can bake in a check for every single next/link that doesn't go through express so that I can make sure the user is logged in?

like image 763
bryan Avatar asked Jul 19 '18 15:07

bryan


3 Answers

Tim from the next chat helped me solve this. Solution can be found here but I will quote him so you all can see:

  • You can do the check in _app.js getInitialProps and redirect like this
  • Example of how to use it
  • _app.js documentation

I've also created an example skeleton template you can take a look at.

--

EDIT July 2021 - WARNING: This is an outdated solution and has not been confirmed to work with the latest versions of next.js. Use skeleton template at your own risk.

like image 132
bryan Avatar answered Oct 19 '22 18:10

bryan


Edit: Updated answer for Next 12.2+

Note: The below contents is copied from the official blog post since SO generally discourages links that can become stale/dead over time https://nextjs.org/blog/next-12-2#middleware-stable

Middleware is now stable in 12.2 and has an improved API based on feedback from users.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

// If the incoming request has the "beta" cookie
// then we'll rewrite the request to /beta
export function middleware(req: NextRequest) {
  const isInBeta = JSON.parse(req.cookies.get('beta') || 'false');
  req.nextUrl.pathname = isInBeta ? '/beta' : '/';
  return NextResponse.rewrite(req.nextUrl);
}

// Supports both a single value or an array of matches
export const config = {
  matcher: '/',
};

Migration guide

https://nextjs.org/docs/messages/middleware-upgrade-guide Breaking changes

  • No Nested Middleware
  • No Response Body
  • Cookies API Revamped
  • New User-Agent Helper
  • No More Page Match Data
  • Executing Middleware on Internal Next.js Requests

How to upgrade

You should declare one single Middleware file in your application, which should be located next to the pages directory and named without an _ prefix. Your Middleware file can still have either a .ts or .js extension.

Middleware will be invoked for every route in the app, and a custom matcher can be used to define matching filters. The following is an example for a Middleware that triggers for /about/* and /dashboard/:path*, the custom matcher is defined in an exported config object:

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  return NextResponse.rewrite(new URL('/about-2', request.url))
}

// Supports both a single string value or an array of matchers
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

Edit: Outdated answer for next > 12 and < 12.2

With the release of Next.js 12, there's now beta support for middleware using Vercel Edge Functions.

https://nextjs.org/blog/next-12#introducing-middleware

Middleware uses a strict runtime that supports standard Web APIs like fetch. > This works out of the box using next start, as well as on Edge platforms like Vercel, which use Edge Functions.

To use Middleware in Next.js, you can create a file pages/_middleware.js. In this example, we use the standard Web API Response (MDN):

// pages/_middleware.js

export function middleware(req, ev) {
  return new Response('Hello, world!')
}

JWT Authentication example

  • https://github.com/vercel/examples/tree/main/edge-functions/jwt-authentication

in next.config.js:

const withTM = require('@vercel/edge-functions-ui/transpile')()

module.exports = withTM()

in pages/_middleware.js:

import { NextRequest, NextResponse } from 'next/server'
import { setUserCookie } from '@lib/auth'

export function middleware(req: NextRequest) {
  // Add the user token to the response
  return setUserCookie(req, NextResponse.next())
}

in pages/api/_middleware.js:

import type { NextRequest } from 'next/server'
import { nanoid } from 'nanoid'
import { verifyAuth } from '@lib/auth'
import { jsonResponse } from '@lib/utils'

export async function middleware(req: NextRequest) {
  const url = req.nextUrl

  if (url.searchParams.has('edge')) {
    const resOrPayload = await verifyAuth(req)

    return resOrPayload instanceof Response
      ? resOrPayload
      : jsonResponse(200, { nanoid: nanoid(), jwtID: resOrPayload.jti })
  }
}

in pages/api/index.js:

import type { NextApiRequest, NextApiResponse } from 'next'
import { verify, JwtPayload } from 'jsonwebtoken'
import { nanoid } from 'nanoid'
import { USER_TOKEN, JWT_SECRET_KEY } from '@lib/constants'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'GET') {
    return res.status(405).json({
      error: { message: 'Method not allowed' },
    })
  }
  try {
    const token = req.cookies[USER_TOKEN]
    const payload = verify(token, JWT_SECRET_KEY) as JwtPayload
    res.status(200).json({ nanoid: nanoid(), jwtID: payload.jti })
  } catch (err) {
    res.status(401).json({ error: { message: 'Your token has expired.' } })
  }
}
like image 24
Nate Radebaugh Avatar answered Oct 19 '22 20:10

Nate Radebaugh


There is no middleware for no API routes in NextJS, but there are HOCs, which you can use to connect to db - select the user, etc: https://hoangvvo.com/blog/nextjs-middleware

like image 3
gdfgdfg Avatar answered Oct 19 '22 18:10

gdfgdfg