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.
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"))
// 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")
}
}
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.
Not really a thing but something the naysayers will bring up.
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
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.
Seems redundant.
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