New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Null token in middleware authorized callback #5170
Comments
+1 for this. I am having this issue as well. |
+1 too, Next 12.2.5 didn't solve this problem for me. |
I can reproduce, getting the following error when navigating on the [next-auth][error][JWT_SESSION_ERROR]
https://next-auth.js.org/errors#jwt_session_error decryption operation failed {
message: 'decryption operation failed',
stack: 'JWEDecryptionFailed: decryption operation failed\n' +
' at gcmDecrypt (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/jose@4.9.0/node_modules/jose/dist/node/cjs/runtime/decrypt.js:67:15)\n' +
' at decrypt (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/jose@4.9.0/node_modules/jose/dist/node/cjs/runtime/decrypt.js:92:20)\n' +
' at flattenedDecrypt (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/jose@4.9.0/node_modules/jose/dist/node/cjs/jwe/flattened/decrypt.js:119:52)\n' +
' at async compactDecrypt (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/jose@4.9.0/node_modules/jose/dist/node/cjs/jwe/compact/decrypt.js:18:23)\n' +
' at async jwtDecrypt (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/jose@4.9.0/node_modules/jose/dist/node/cjs/jwt/decrypt.js:8:23)\n' +
' at async Object.decode (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/next-auth@4.10.3_biqbaboplfbrettd7655fr4n2y/node_modules/next-auth/jwt/index.js:64:7)\n' +
' at async Object.session (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/next-auth@4.10.3_biqbaboplfbrettd7655fr4n2y/node_modules/next-auth/core/routes/session.js:41:28)\n' +
' at async NextAuthHandler (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/next-auth@4.10.3_biqbaboplfbrettd7655fr4n2y/node_modules/next-auth/core/index.js:143:27)\n' +
' at async NextAuthNextHandler (/Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/next-auth@4.10.3_biqbaboplfbrettd7655fr4n2y/node_modules/next-auth/next/index.js:23:19)\n' +
' at async /Users/p/Documents/middleware-issue-reproducer/node_modules/.pnpm/next-auth@4.10.3_biqbaboplfbrettd7655fr4n2y/node_modules/next-auth/next/index.js:59:32',
name: 'JWEDecryptionFailed'
} I think this looks like an upstream error related to the new middleware changes? Edit: This is weird, when I defined a Edit2: I'm pretty confident that the combination of Edit3: Seems like next-auth no longer generates a secret for you in dev (https://next-auth.js.org/getting-started/upgrade-v4#missing-secret) |
Next 12.2.5 didn´t solve the issue for me either. Everything works fine when running next locally on windows, but when using the same project inside a docker container locally on windows, the token is always null. EnvironmentWindows |
I've been struggling with this one for a couple of days, nothing helped much. Please do check your env vars. In my case it was a mismatch between
didn't want to have too many secrets out there. Thus, locally my docker image and prod build were using my local .env file but on k8s cluster there was the whole different value for |
Hi folks, as @ilyadoroshin and @awareness481 suggested, the problem is missing the Output:
P/S: @Biratus For config matcher, you don't need to define both |
@ThangHuuVu cool! Perhaps it would be easier if next-auth is solid on the |
@ThangHuuVu I scaffolded a new app, have |
@jrandallw please make sure your |
I haven’t been passing a secret to NextAuth. The documentation makes it seem as though it will default to using the |
From the documentation: NEXTAUTH_SECRET Used to encrypt the NextAuth.js JWT, and to hash email verification tokens. This is the default value for the secret option in NextAuth and Middleware. |
@jrandallw I tried it too and still doesn't work.
|
I'm having the same problem, have taken all the permutations as @Biratus and others have, and am also still getting a null token. |
I guess the best bet to debug it now is to make an api-route that displays both vars as json, to make sure that it's set and nextjs can see it |
I am facing the same problem. When debugging I see that _decode is throwing "Invalid Compact JWE", as you can see in the catch that's why it returns null. next-auth/packages/next-auth/src/jwt/index.ts Lines 110 to 117 in 448ec10
The error happens even though the token and secret match the correct values. |
Yep, tried it too and the value is good. Tried logging the secret value in Edit 1: @leandrorlemos In my case, the token is an empty string in jwt/index.js. So I did some debugging in this file, in the
Those value give a next-auth/packages/next-auth/src/jwt/index.ts Line 105 in 448ec10
I am fairly confident we've identified the problem. Edit2: I HAVE RESOLVED MY PROBLEM !! So the problem as said just before, is I had the wrong cookie name value for the token. To get the correct I had to change the NEXTAUTH_URL env var to https://localhost. And it works just fine... |
The suggested solutions didn't solve my issue. I´ve tested the token extraction and decoding - this part is working as expected: Content of NEXTAUTH_SECRET=<mySecret>
NEXTAUTH_URL=http://localhost:3000 Content of import { NextApiRequest, NextApiResponse } from 'next';
import { getToken } from 'next-auth/jwt';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const secret = process.env.NEXTAUTH_SECRET
? process.env.NEXTAUTH_SECRET
: 'NEXTAUTH_SECRET not found!';
const decoded_token_from_request = await getToken({ req, secret });
res.status(200).json({
secret,
decoded_token_from_request,
});
} Response of /api/test/token: {"secret":"<mySecret>":{"name":"","email":"","userRole":"admin","authProvider":"CredentialsProvider","iat":1661169332,"exp":1663761332,"jti":"7013b8a1-83c6-413b-880e-01027ff41c1a"}} Content of import { withAuth } from 'next-auth/middleware';
export default withAuth({
callbacks: {
authorized({ req, token }) {
console.log('middleware - withAuth - callbacks - authorized');
console.log('req: ', req);
console.log('token: ', token);
const path = req.nextUrl.pathname;
// `/admin` requires admin role
if (path.startsWith('/admin')) {
return token?.userRole === 'admin';
}
// every other route in config only requires the user to be logged in
return !!token;
},
},
});
export const config = {
matcher: ['/admin/:path*', '/restricted/:path*'],
}; Console after navigating to /admin:
The token is always "null". |
I believe I've figured out my issue as well. In my case, I'm using the Next.js, EmailProvider, and the PrismaAdapter. According to the session docs, the default
I'm not sure if I'm accessing the token correctly now, but after setting Note that I didn't need to do anything with
For completeness, here's the relevant code:
import NextAuth, { NextAuthOptions } from "next-auth";
import EmailProvider from "next-auth/providers/email";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],
pages: {
signIn: "/signin",
},
session: {
strategy: "jwt", // See https://next-auth.js.org/configuration/nextjs#caveats, middleware (currently) doesn't support the "database" strategy which is used by default when using an adapter (https://next-auth.js.org/configuration/options#session)
},
};
export default NextAuth(authOptions);
export { default } from "next-auth/middleware";
export const config = { matcher: ["/home"] }; Getting a |
I´m also using the PrismaAdapter and have set Does anyone have further suggestions on how to resolve the(my) issue?
import NextAuth from 'next-auth';
import type { NextAuthOptions } from 'next-auth';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import CredentialsProvider from 'next-auth/providers/credentials';
import AzureADProvider from 'next-auth/providers/azure-ad';
import { prisma } from '../../../lib-server/prisma';
export const dbAdapter = PrismaAdapter(prisma);
export const authOptions: NextAuthOptions = {
providers: [
/* CredentialsProvider, AzureADProvider are used here */
],
adapter: dbAdapter,
session: {
// Choose how you want to save the user session.
// The default is `"jwt"`, an encrypted JWT (JWE) stored in the session cookie.
// If you use an `adapter` however, we default it to `"database"` instead.
// You can still force a JWT session by explicitly defining `"jwt"`.
// When using `"database"`, the session cookie will only contain a `sessionToken` value,
// which is used to look up the session in the database.
strategy: "jwt",
},
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
// set user role once at sign in
user.role = "admin"; // add role info - custom user property
// Sign in request using OAuth provider
if (profile !== undefined)
user.authProvider = 'OAuth provider'; // add provider info, custom user property
return true;
// Return false to display a default error message
// Or you can return a URL to redirect to: return '/unauthorized'
},
async redirect({ url, baseUrl }) {
if (url.startsWith(baseUrl)) return url;
// Allows relative callback URLs
else if (url.startsWith("/")) return new URL(url, baseUrl).toString();
return baseUrl;
},
async jwt({ token, user, account, profile, isNewUser }) {
/* Use an if branch to check for the existence of parameters (apart from token). If they exist, this means that the callback is being invoked for the first time (i.e. the user is being signed in). This is a good place to persist additional data like an access_token in the JWT. Subsequent invocations will only contain the token parameter. */
// Persist the OAuth access_token to the token right after signIn
if (account) {
token.accessToken = account.access_token;
}
if (user) {
// is set only once of login of user
// add custom properties to jwt token
token.userRole = user.role;
token.authProvider = user.authProvider;
}
return token;
},
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.accessToken = token.accessToken;
// add custom properties to session
session.userRole = token.userRole;
session.authProvider = token.authProvider;
return session;
},
}
};
export default NextAuth(authOptions); |
Check your |
I finally solved my issue by manually deleting the docker volume of my next container. At the moment i don´t know why the "old" volume caused this issues. Maybe my directory mounting config is incorrect... Here is the relevant part of my docker-compose file: services:
next-app:
build:
context: ./next-app
target: dev
env_file: docker/env_dev/next-app.env
environment:
NODE_ENV: development
volumes:
- "./next-app:/usr/src/app"
- /usr/src/app/node_modules
- /usr/src/app/.next
ports:
- "3000:3000"
- "9229:9229"
depends_on:
- pgsql-database
command: ["yarn", "run", "dev:inspect"]
... |
I fix this by getting token from session then decrypt it
|
Almost a year old and no solution. What a travesty. I have everything set up correctly, but using |
I had the same issue. At the beginning try to change your next-auth version. |
Any updates ? |
any updates ? |
My teammate encountered the similar issue today, and the solution was to update Node.js to v18.17.1 and npm to v9.6.7. |
For me I needed to call getToken otherwise token would return null.
|
I have the same issue |
Issue: Null token in middleware authorized callback in Vercel Deployment Cause: The issue is occurring because the user failed to sign in properly. Because invalid NEXTAUTH_URL Solution: To resolve this, you should update the NEXTAUTH_URL variable in the environment (env) file to match the deployment URL. For example:
|
My issue was that I was using a different cookie name - the config I used to instantiate Solved using export const nextCookieOpts = {
// default cookie options
sessionToken: {
name: `${cookiePrefix}${appName}.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
},
}
export default withAuth({
callbacks: {
authorized({ req, token }) {
const pathname = req.nextUrl.pathname;
console.log(env.NEXTAUTH_URL);
if (pathname.startsWith("/static/")) {
return true;
}
// `/admin` requires admin role
if (pathname === "/admin") {
return token?.userRole === "admin";
}
// `/me` only requires the user to be logged in
return !!token;
},
},
cookies: nextCookieOpts,
}); |
Perhaps my 2 cents can help someone here. I had the same problem as many in this thread. My solution hosted on DO was: Original Solution (after also following many tips in this thread): AHHHHHHHHHHH :S |
This resolved my issue instantly (thank you!), one that baffled me for a little bit. This is my first time using this library and I'm wondering whether the way I'm using it is not a common use case.
It was very non-obvious to me that I needed to specify session: { strategy: "jwt" }. I had used this email tutorial as a reference. It doesn't mention anything about Middleware. I added a PR to the docs to hopefully save others time in the future. |
Ooof, finally. After so many time trying to fix this, none of the above answers worked for me. let isAuthenticated = false;
request.headers.forEach((value, key) => {
if (value.includes(cookieName) || key.includes(cookieName)) {
isAuthenticated = true;
}
}); Note: This code ONLY checks for session cookie, it doesn't validate if it's still valid, or if it's even a valid session. You can make it more robust by validating the content in it correctly with your NEXTAUTH_SECRET env variable. This kept me blocked for more than 12 hours by now 😞, I need to deliver my MVP and don't care about this vulnerability in the first day 😆. |
A work around for the problem is here!!Unfortunately those of us who are using alternate authentication methods such as session-based authentication don't have anything out of the box, and implementing a middleware like that yourself is trickier than you would expect, because you cannot simply pass the request in your middleware to getSession This seems to be because the next-auth accesses headers via req.headers.cookie, but the type of the headers inside middleware is not an object, but a Headers object which must be accessed through req.headers.get("cookie") I have implemented a middleware that works for session-based authentication. It does this by converting the relevant part of the request headers to an object import type { NextFetchEvent, NextRequest } from 'next/server';
import { getSession } from 'next-auth/react';
import { NextResponse } from 'next/server';
export async function middleware(req: NextRequest, ev: NextFetchEvent) {
const requestForNextAuth = {
headers: {
cookie: req.headers.get('cookie'),
},
};
const session = await getSession({ req: requestForNextAuth });
if (session) {
console.log(session);
// validate your session here
return NextResponse.next();
} else {
// the user is not logged in, redirect to the sign-in page
const signInPage = '/auth/signin';
const signInUrl = new URL(signInPage, req.nextUrl.origin);
signInUrl.searchParams.append('callbackUrl', req.url);
return NextResponse.redirect(signInUrl);
}
} However I think this means that an extra fetch call will be made to the next-auth backend. One in the middleware, and one later on if you want to access the session in API calls. |
I solved the problem after few days of investigating. That's about NEXTAUTH_URL in .env value, that value was NEXTAUTH_URL="http://localhost:3000" in my local env, i was think that double quote makes no sense but actually that's the problem. I tried to update that value to NEXTAUTH_URL=http://localhost:3000 and finally it worked. |
Environment
System:
OS: Linux 5.15 Ubuntu 20.04.4 LTS (Focal Fossa)
CPU: (4) x64 Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
Memory: 3.83 GB / 15.07 GB
Container: Yes
Shell: 5.0.17 - /bin/bash
Binaries:
Node: 16.13.0 - ~/.nvm/versions/node/v16.13.0/bin/node
npm: 8.1.0 - ~/.nvm/versions/node/v16.13.0/bin/npm
Browsers:
Brave Browser: 104.1.42.88
Chrome: 104.0.5112.79
Firefox: 103.0
npmPackages:
next: ^12.2.5 => 12.2.5
next-auth: 4.10.3 => 4.10.3
react: 18.2.0 => 18.2.0
Reproduction URL
https://github.com/Biratus/middleware-issue-reproducer
Describe the issue
The issue has been discussed but closed with next@12.2.5: #5008 which I have followed for the past couple weeks.
However the issue is still occurring with next@12.2.5:
The middleware callback "authorized" receives a null value for the "token" property.
I am using jwt strategy and credentials.
Here is the middleware code I use, pretty straightforward.
I linked a reproducer in which I added a description in the README
There are 3 useful pages in the app:
I have included logs in the middleware.js file. In the authorized callback and middleware function.
The /login page does sign in the user, we can see the token being created in the "storage" tab in the developer console.
The /private page redirects to /login even when there is a token.
How to reproduce
Clear all cookies.
Go to login page: localhost:3000/login
Click on the sign in button.
Check that a token is added to cookies.
Navigate to localhost:3000/private
The sever logs shows: "authorized /private null" (authorized callback log)
Expected behavior
The /private page should be visible when a user is authenticated/when there is a token in cookies.
Warning: I am new to nextJS and NodeJS developpement in general. However I have had my fair share of Spring and JS dev. So there might be some things I don't do correctly.
Thank you all for taking the time.
The text was updated successfully, but these errors were encountered: