nextauthjs / nextauthjs/next-auth
Unable to redirect within authorization callback
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 28.4k
- Forks
- 4k
- PR merge metrics
- No merged PRs in 30d
Description
Environment
System:
OS: Linux 5.15 Ubuntu 22.04.5 LTS 22.04.5 LTS (Jammy Jellyfish)
CPU: (20) x64 Intel(R) Core(TM) i9-10850K CPU @ 3.60GHz
Memory: 8.32 GB / 15.56 GB
Container: Yes
Shell: 5.8.1 - /usr/bin/zsh
Binaries:
Node: 20.17.0 - ~/.volta/tools/image/node/20.17.0/bin/node
npm: 10.8.2 - ~/.volta/tools/image/node/20.17.0/bin/npm
pnpm: 9.9.0 - ~/.volta/bin/pnpm
Browsers:
Chrome: 131.0.6778.108
npmPackages:
next: 15.1.5 => 15.1.5
next-auth: 5.0.0-beta.25 => 5.0.0-beta.25
react: ^19.0.0 => 19.0.0
Reproduction URL
https://github.com/ralphsmith80/authjs-authorized-callback/blob/main/src/auth.ts#L39
Describe the issue
Issue a redirect in the authorized callback does not work. I've noted that it will works by adjusting the code in one of two ways. Either by adjusting the middleware or by not overriding the authorized callback, but if you need to apply middleware and override the authorized callback I think you're stuck.
Also located in the repo here
Breaking test case
export { auth as middleware } from '@/auth'
// Optionally, don't invoke Middleware on some paths
export const config = {
/*
* Match all routes except for the following:
* - api/* (API routes)
* - _next/static/* (static files)
* - _next/image* (image optimization files)
* - favicon.ico
* - robots.txt
* - home page (root route)
*/
matcher: [
'/((?!api|_next/static|_next/image|images|fonts|favicon.ico|robots.txt|$).*)',
],
}
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import { type Provider } from 'next-auth/providers'
const providers: Provider[] = [
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email', placeholder: 'smith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const users = [
{
id: '1',
email: 'test@test.com',
name: 'test',
password: 'pass',
},
]
const user = users.find(
(user) =>
user.email === credentials?.email &&
user.password === credentials?.password
)
return user ? { id: user.id, email: user.email, name: user.name } : null
},
}),
]
export const { auth, handlers, signIn, signOut } = NextAuth({
session: {
strategy: 'jwt',
maxAge: 5, // 5 seconds for testing
},
providers,
callbacks: {
authorized: async ({ auth }) => {
// Logged in users are authenticated, otherwise redirect to login page based on the middleware matcher
return !!auth
},
},
})
Working with middleware matcher adjustment
This is less ideal because it require the page to not be protected by middleware, but I'm showing it for completeness. The change is to modify the matcher to include the previously protected page dashboard. This results in the server action being invoked which contains the necessary protection and redirect.
export { auth as middleware } from '@/auth'
// Optionally, don't invoke Middleware on some paths
export const config = {
/*
* Match all routes except for the following:
* - api/* (API routes)
* - _next/static/* (static files)
* - _next/image* (image optimization files)
* - favicon.ico
* - robots.txt
* - home page (root route)
*/
matcher: [
'/((?!api|_next/static|_next/image|images|fonts|favicon.ico|robots.txt|dashboard$).*)',
],
}
Working without the authorized callback override
This is more ideal since it allows the page to be protected by middleware. I've removed the authorized callback in the auth.ts file. The down side is that this will not work if you need to override the authorized callback.
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import { type Provider } from 'next-auth/providers'
const providers: Provider[] = [
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email', placeholder: 'smith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const users = [
{
id: '1',
email: 'test@test.com',
name: 'test',
password: 'pass',
},
]
const user = users.find(
(user) =>
user.email === credentials?.email &&
user.password === credentials?.password
)
return user ? { id: user.id, email: user.email, name: user.name } : null
},
}),
]
export const { auth, handlers, signIn, signOut } = NextAuth({
session: {
strategy: 'jwt',
maxAge: 5, // 5 seconds for testing
},
providers,
callbacks: {
},
})
Working with middleware matcher and authorized callback
This is the solution if you want your matcher protect that page and you want to override the authorized callback.
This does not work. How can we make this work?
import { NextResponse } from 'next/server'
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import { type Provider } from 'next-auth/providers'
const providers: Provider[] = [
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email', placeholder: 'smith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const users = [
{
id: '1',
email: 'test@test.com',
name: 'test',
password: 'pass',
},
]
const user = users.find(
(user) =>
user.email === credentials?.email &&
user.password === credentials?.password
)
return user ? { id: user.id, email: user.email, name: user.name } : null
},
}),
]
export const { auth, handlers, signIn, signOut } = NextAuth({
session: {
strategy: 'jwt',
maxAge: 5, // 5 seconds for testing
},
providers,
callbacks: {
authorized: async ({ auth, request }) => {
// Logged in users are authenticated, otherwise redirect to login page based on the middleware matcher
if (!auth) {
const url = `${new URL(request.nextUrl.origin)}api/auth/signin`
console.log('####not authorized', url)
// return NextResponse.json('Invalid auth token', { status: 401 })
return NextResponse.redirect(url)
// return Response.redirect(url)
// return false
}
return true
},
},
})
How to reproduce
- clone the repo
- install dependencies
- run the dev server
- currently it is in a working state with a 5 second timeout on the session
- login with the mock credentials
test@test.com,pass - wait 5+ seconds
- click either of the buttons
- works
- uncomment the
authorizedcallback - repeat steps
5,6, and7 - fails
Expected behavior
The user should be redirected to the sign in page.
Actual behavior
A missing CSRF error is logged in the server console.
[auth][error] MissingCSRF: CSRF token was missing during an action signin. Read more at https://errors.authjs.dev#missingcsrf
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked reproduction, especially middleware.ts and src/auth.ts, and run the documented steps after the five-second session timeout. Compare behavior with and without the authorized callback and with the matcher adjustment. Done means the protected-page action redirects to the sign-in page without logging MissingCSRF.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, node.js, react, typescript
- Domain
- api, authentication, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100