Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungTCP vs UDP: The Two Ways to Move Data, and Why Neither Is "Better"(21.09.2026 um 09:31 Uhr)
Sichere ProgrammierungBukan Sekadar Variabel, Tapi Nyawa dari Aplikasi Kamu! 🚀(21.09.2026 um 09:36 Uhr)
Sichere ProgrammierungAI voice agent for customer service: what stops callers hanging up?(21.09.2026 um 09:42 Uhr)
Sichere ProgrammierungReading a small model's confidence instead of its prose(21.09.2026 um 09:47 Uhr)
Sichere ProgrammierungTCP vs UDP: The Two Ways to Move Data, and Why Neither Is "Better"(21.09.2026 um 09:31 Uhr)
Sichere ProgrammierungBukan Sekadar Variabel, Tapi Nyawa dari Aplikasi Kamu! 🚀(21.09.2026 um 09:36 Uhr)
Sichere ProgrammierungAI voice agent for customer service: what stops callers hanging up?(21.09.2026 um 09:42 Uhr)
Sichere ProgrammierungReading a small model's confidence instead of its prose(21.09.2026 um 09:47 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

CSRF tokens in Nextjs

Recently, I was working on a project with Next.js (using TypeScript), where I needed to implement CSRF tokens to secure some validations in the middleware.ts file. During the process, I encountered some issues that I'll share in this post…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Recently, I was working on a project with Next.js (using TypeScript), where I needed to implement CSRF tokens to secure some validations in the middleware.ts file. During the process, I encountered some issues that I'll share in this post in case it helps someone else.



First, I did some research online, and apparently, the most commonly used library for this purpose is csrf. So, I installed it and tried a quick test in the middleware.ts file (which doesn’t exist by default) to see how it worked:




// src/middleware.ts

import { NextResponse } from 'next/server'
import Tokens from 'csrf'

export async function middleware() {
const responseNext = NextResponse.next()

const tokens = new Tokens()
const secret = tokens.secretSync()
const token = tokens.create(secret)

console.log(`🚀🚀🚀 -> token:`, token)

return responseNext
}

export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}






And immediately, I got an error 🥲



middleware file error



The error is pretty straightforward, but at the time, it took me a while to find the reason and solution. Essentially, the error says that the rndm library is using the assert function in its code, but this function doesn’t exist (according to the error).



After some investigation, I found important information about this:




  • I didn’t install the rndm library, which causes the error. It’s a dependency of the csrf library, which is why the error appears when using the latter.

  • The middleware.ts file in Next.js runs in the edge runtime instead of the Node.js runtime, so it doesn’t support some Node.js APIs, including the assert function.

  • Therefore, the csrf library cannot be used in the middleware.ts file.









Alternative Solution



Once I identified the problem, I started looking for possible alternatives and found a very good one: @edge-csrf/nextjs. It’s a very simple implementation for Next.js to use within the middleware.ts file.



@edge-csrf/nextjs library



⚠️⚠️⚠️ However, while it’s a great and easy-to-use implementation, for my specific problem, I needed more control and customization. So, I had to make some different adjustments to my code.






Custom Solution



Although I wasn’t going to use the @edge-csrf/nextjs library directly, I decided to use its core, which is another library called @edge-csrf/core. This allowed me to create a custom solution to handle CSRF tokens in the middleware.ts file.



For this tutorial, I made a very basic code implementation that does the minimum to create and verify a CSRF token in Next.js middleware. Here’s how it works:




  • The first time the website’s page loads (a GET request), the middleware creates a cookie with the CSRF token and attaches it to the response so that it can be used later.

  • If it’s not the first time the website loads (a GET request), the cookie might already be expired. In that case, a new cookie is also created to replace the expired one.

  • For this tutorial, cookies expire in 30 seconds for quick testing. In a production project, these cookies typically last 1 hour (more or less) depending on your needs.

  • Then, from the frontend of the website, whenever a PUT, PATCH, or DELETE request is made to the API, a header with the CSRF token is attached so it can be verified.

  • When the API receives any PUT, PATCH, or DELETE request, the middleware first checks whether the header attached to the request exists and whether it’s a valid CSRF token. If not, it generates an error response, and the request ends there. But if the token is valid, the middleware allows the request to proceed to the next step to execute the endpoint.

  • For this tutorial, I’m only validating the token for PUT, PATCH, and DELETE requests, and I’m omitting POST requests. Of course, this is fully customizable and can be adapted to your needs.









Code



👇 Some constants.




// src/constants.ts

export const IS_PRODUCTION: boolean = process.env.NODE_ENV === 'production'

export const CSRF_TOKEN_NAME: string = 'csrftoken'

export const ERROR_CODE_INVALID_CSRF: string = 'middleware/invalid-csrf-token'
export const ERROR_CODE_INTERNAL_SERVER: string = 'server/internal-server-error'






👇 My implementation of the @edge-csrf/core library to generate and verify CSRF tokens.




// src/utils/csrfTokens.ts

import {
createSecret,
createToken,
verifyToken,
atou,
utoa,
} from '@edge-csrf/core'

const CONFIG = {
SALT_LENGTH: 49, // Custom number between 1 and 255
SECRET_LENGTH: 48, // Custom number between 1 and 255
}

const secretUint8Array = createSecret(CONFIG.SECRET_LENGTH)

export async function generateCsrfToken(): Promise<string> {
const tokenUint8Arr = await createToken(secretUint8Array, CONFIG.SALT_LENGTH)
const tokenStr = utoa(tokenUint8Arr)
return tokenStr
}

export async function verifyCsrfToken(token: string): Promise<boolean> {
const tokenUint8Arr = atou(token)
const isValid = await verifyToken(tokenUint8Arr, secretUint8Array)
return isValid
}






👇 My middleware implementation.




  • For each incoming request, if the request is PUT, PATCH, or DELETE, the middleware checks whether the headers contain the CSRF token and if it’s valid. If it’s valid, the request proceeds to the corresponding endpoint in the API. If not, an error response is returned.

  • On the other hand, if the request is GET and the cookie with the CSRF token doesn’t exist (this is the first time visiting the website, or the cookie has expired), a new token is generated and attached as a cookie to the response.




// src/middleware.ts

import { NextResponse, NextRequest } from 'next/server'

import {
IS_PRODUCTION,
CSRF_TOKEN_NAME,
ERROR_CODE_INVALID_CSRF,
} from '@/constants'
import { generateCsrfToken, verifyCsrfToken } from '@/utils/csrfTokens'

export async function middleware(request: NextRequest) {
const responseNext = NextResponse.next()

if (
request.method === 'PUT' ||
request.method === 'PATCH' ||
request.method === 'DELETE'
) {
const invalidCsrfTokenResponse = NextResponse.json(
{ message: ERROR_CODE_INVALID_CSRF },
{ status: 403 },
)

try {
const csrfRequestToken = request.headers.get(CSRF_TOKEN_NAME) ?? ''
const isTokenValid = await verifyCsrfToken(csrfRequestToken)

if (!isTokenValid) {
return invalidCsrfTokenResponse
}
} catch (error) {
console.error(error)
return invalidCsrfTokenResponse
}
} else if (
request.method === 'GET' &&
!request.cookies.has(CSRF_TOKEN_NAME)
) {
try {
const csrfResponseToken = await generateCsrfToken()
responseNext.cookies.set(CSRF_TOKEN_NAME, csrfResponseToken, {
sameSite: 'lax',
httpOnly: false, // The cookie needs to be accessible from JS in the frontend
secure: IS_PRODUCTION,
maxAge: 30, // 30 seconds for this tutorial example
})
} catch (error) {
console.error(error)
}
}

return responseNext
}

export const config = {
// This matcher allows filtering middleware to run on specific paths.
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/

'/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}






👇 PUT /login endpoint, which for this tutorial only returns a JSON; but it’s executed only if the middleware allows the request based on the CSRF token validation.




// src/app/api/login/route.ts

import { NextResponse, NextRequest } from 'next/server'

import { ERROR_CODE_INTERNAL_SERVER } from '@/constants'

export async function PUT(request: NextRequest) {
try {
// Fake login response.
const responsePayload = {
message: 'You are now logged in',
}
return new Response(JSON.stringify(responsePayload), { status: 200 })
} catch (error) {
console.error(error)

return NextResponse.json(
{ message: ERROR_CODE_INTERNAL_SERVER },
{ status: 500 },
)
}
}






👇 An action to delete the cookie manually if needed.




// src/actions/deleteCsrfCookieAction.ts
'use server'

import { cookies } from 'next/headers'

import { CSRF_TOKEN_NAME } from '@/constants'

export default async function deleteCsrfCookieAction(): Promise<void> {
try {
const cookieStore = await cookies()
const csrfCookie = cookieStore.get(CSRF_TOKEN_NAME)

if (csrfCookie) {
cookieStore.delete(CSRF_TOKEN_NAME)
}
} catch (error) {
console.error(error)
}
}






👇 Finally, the frontend code that makes requests to the API.




  • For the PUT request, it first retrieves the CSRF token from the cookie (if it exists) and attaches it as a header in the request so that the middleware can validate it upon receiving the request.

  • There’s also a function to generate a new cookie, which simply deletes the current cookie (if it still exists) and generates a new one by refreshing the page, as the middleware will create a new cookie if it doesn’t find one in the GET request.




// src/app/components/Login/Login.tsx
'use client'

import { useCallback, useState, Suspense } from 'react'
import { useRouter } from 'next/navigation'

import { CSRF_TOKEN_NAME } from '@/constants'
import getCookie from '@/utils/getCookie'
import deleteCsrfCookieAction from '@/actions/deleteCsrfCookieAction'

function BaseComponent() {
const router = useRouter()

const [isLoading, setIsLoading] = useState<boolean>(false)
const [includeToken, setIncludeToken] = useState<boolean>(false)
const [resultMessage, setResultMessage] = useState<string>('')

const handleLogin = useCallback(async () => {
try {
setResultMessage('')
setIsLoading(true)
const csrfTokenCookie = getCookie(CSRF_TOKEN_NAME)
const sessionRes = await fetch('/api/login', {
method: 'PUT',
headers: {
[CSRF_TOKEN_NAME]: includeToken ? csrfTokenCookie : '',
},
})
const sessionData = await sessionRes.json()
setResultMessage(sessionData?.message)
} catch (error) {
console.error(error)
setResultMessage('Unexpected error occurred.')
} finally {
setIsLoading(false)
}
}, [includeToken])

const generateNewCookie = useCallback(async () => {
try {
setResultMessage('')
await deleteCsrfCookieAction() // Delete previous cookie if still exists.
router.refresh() // Sufficient to generate a new cookie because of the middleware.
} catch (error) {
console.error(error)
}
}, [router])

return (
<div className='border border-[#1d9bf0] rounded-xl p-5 text-lg'>
<div>
<input
type='checkbox'
id='send_token'
checked={includeToken}
onChange={(e) => {
setResultMessage('')
setIncludeToken(e.target.checked)
}}
/>
<label htmlFor='send_token' className='ml-2 cursor-pointer'>
{`Send token in request`}
</label>
</div>

<div className='pt-3'>
<button
type='button'
onClick={handleLogin}
disabled={isLoading}
className='py-1 px-4 rounded bg-[#1d9bf0] font-medium min-w-56 w-full'
>
{includeToken ? 'Request (with token)' : 'Request (without token)'}
</button>
</div>

<div className='pt-3'>
<button
type='button'
onClick={generateNewCookie}
disabled={isLoading}
className='py-1 px-4 rounded bg-[#1d9bf0] font-medium min-w-56 w-full'
>
{'Generate new cookie'}
</button>
</div>

<div className='pt-3 text-[#1d9bf0] font-semibold'>{`RESULT: '${resultMessage}'`}</div>
</div>
)
}

export default function Login() {
return (
<Suspense>
<BaseComponent />
</Suspense>
)
}






👇 And the main entry page.




// src/app/page.tsx

import Login from '@/components/Login/Login'

export default function Home() {
return (
<main className='p-2 text-lg leading-5 sm:pt-10'>
<div className='border border-[#1d9bf0] rounded-xl p-5 max-w-md mx-auto'>
<Login />
</div>
</main>
)
}









Demo



You can run the project locally or try the live demo. Open the network tab in the browser’s developer tools to see the response:



✅ When the header contains a valid token.



valid token response



❌ When the header doesn’t contain the token or the token is invalid.



invalid token response









Conclusion



If, like me, you want to implement CSRF tokens in Next.js and the standard solution doesn’t work for you or you want more control over the middleware, this can be a viable option or a base example to create your own implementation. I hope it helps!






Links



🚀 Live Demo



💻 GitHub Repo

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten CSRF tokens in Nextjs

Thematisch verwandte Begriffe: CSRF, tokens, Nextjs · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94030 | A security vulnerability has been detected in SerenityOS up to 3d83e4509…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick