Next Middleware


Uncategorized

Updated May 15th, 2022

The Next docs here. Examples (including one for jwt) here.

In the jwt example:

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())
}

Note: An older version of middleware was available in NextJS before Next 12 as can be seen in Bruno’s video and another on Jwt Auth video. Also shipped in Next 12 were http streaming, server components, and URL imports, (See fireship video here).

The non-middleware/non-edge implementation:

export default async function handler(req, res) {
  const { postId } = req.query

  if (!postId) {
    res.status(500).json({ error: "postId is required" })
  } else {
    const response = await fetch("https://jsonplaceholder.typicode.com/potsts/${postId}")
    const post = await response.json()

    res.status(200).json({ title: post.title })
  }
}

The non-middleware/non-edge implementation:

// in /pages/api/post/edge/_middleware.js

export async function middleware(req, ev) {
  const url = req.nextUrl
  let postId = null

  url.searchParams.forEach((val, key)) {
    if (key === "postId") {
      postId = val
      return
    }
  }

  if (!postId) {
    return new Response(JSON.stringify({error: "postId is required"}), {
      status: 500,
      headers : {
        "Content-Type: "application/json",
      }
    })
  } else {
    const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
    const post = await response.json()
  }

    return new Response({JSON.stringify({title: post.title})}, {status: 200, headers: {"Content-Type": "application/json"}})
}

Note: This video had the “_middleware.js” file nested deep inside the “/pages/api” folder.

The middleware/Execution order: per the Next docs, If your Middleware is created in “/pages/_middleware.ts,” it will run on all routes within the “/pages” directory. If you do have sub-directories with nested routes, Middleware will run from the top down. For example, if you have /pages/about/_middleware.ts and /pages/about/team/_middleware.ts, /about will run first and then /about/team.

Skillthrive Video

The “logout.js” file at the bottom has “export default withProtect(handler)” and I think this is the old way of wrapping as the “withProtect” middleware function is in it own “middleware” folder that live in the root directory. He also has a “withRoles” middleware function to check the user’s role so only logged in users that are also admins can delete users. Cool pattern that makes sense, (I;m also a fan of the three tiered, user, admin, superadmin)

const withRoles = (handler, ...roles) => {
  return async (req, res) => {
  // Roles in an array
  if(!roles.includes(req.user.role)) {
    return res.status(403).json({
      success: false,
      message: "You do not have permission to perform this action"
    })
  }

    return handler(req, res)
  } 
}

export default withRoles

Chain on making sure the order is correct

export default withProtect(withRoles(handler, "admin"))

Sonny

// pages/_middleware.js

import {getToken} from "next-auth/jwt"
import {NextReponse} from "next/server"

export async function middleware(req) {
  //Token will exist if user is logged in
  const token = await getToken({req, secret: process.env.JWT_SECRET})

  const {pathname} = req.nextUrl

  // Allow the requests if the following is true...
  // 1.) It's a req for next-auth session & provider fetching
  // 2.) The token exists
  if (pathname.includes('/api/auth') || token ) {
    return NextReponse.next()
  }

  // Redirect the to login if they don't have token AND are requesting a protected route
  if (!token && pathname.includes("/protected, /protected-more")) {
  return NextResponse.redirect("/login")
}
  
    
}

Bruno

The old way (micro) you have to explicitly spell out in each endpoint. Creates two functions in one “/pages/api/people” file and wraps one inside of the other:

export default authenticated(async function getPeople( req: NextApiRequest, res: NextApiResponse) {
  const db = await sqlite.open("./mydb.sqlite")
  const people = await db.all("select id, email, name from person")

  res.json(people)
})

Check if a token was sent, if it is a valid token, and if everything is good then call the function handler and everything keeps going.

Vendor Lock In

Not really a thing but something the naysayers will bring up.

Oh Eval

This works but throws warning in the console and fails on build.

import { NextResponse } from "next/server"
import { verify } from "jsonwebtoken"

export default function middleware(req) {
  const { cookies } = req
  const jwt = cookies.SimpleCarCostToken
  const url = req.url

  if (url.includes("/profile") || url.includes("/landing")) {
    if (jwt === undefined) {
      const url = req.nextUrl.clone()
      url.pathname = "/login"
      return NextResponse.rewrite(url)
    }

    try {
      verify(jwt, process.env.JWTSECRET)
      // see the token payload on the server
      //console.log(`user from middleware: ${user.username}`)
      // not sure how to send data from token payload to the client
      //return NextResponse.next()
    } catch (e) {
      const url = req.nextUrl.clone()
      url.pathname = "/login"
      return NextResponse.rewrite(url)
    }
  }

  return NextResponse.next()
}

The flashing try running a production build
-No ESLint configuration detected. Run next lint to begin setup
-Failed to compile
1.) Dynamic Code Evaluation (e. g. ‘eval’, ‘new Function’) not allowed in Middleware pages/_middleware
2.) Build failed because of webpack errors

https://github.com/vercel/next.js/issues/30674

Unsupported APIs – The Edge Runtime has some restrictions…The following JavaScript language features are disabled, and will not work:

eval: Evaluates JavaScript code represented as a string

new Function(evalString): Creates a new function with the code provided as an argument

NOTE: Yet another reason to just use GSSP again. Could also swap JWT for the package called ‘jose’ used in Vercel/edgefunction/jwt example. I was curios why they had to be difficult but this makes sense.

Middleware for Auth then Set non-httpOnly Cookie

Seems redundant.

Sources

Tuomo here is most of the example code above showing implementation post Next 12.

Codedamn here talks about Next 12. He cloned the entire repo with all of the examples from above for quick access in VSCode and my mind is blown over such a simple idea.

Skillthrive here : Shows the pre-Next 12 implementation and a quick comparison to the Express way

Sonny here (see timestamp for middleware)

Bruno here (41:25 starts middleware) outdated