Skip to content
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

Closed
Biratus opened this issue Aug 17, 2022 · 37 comments
Closed

Null token in middleware authorized callback #5170

Biratus opened this issue Aug 17, 2022 · 37 comments
Labels
triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@Biratus
Copy link

Biratus commented Aug 17, 2022

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.

export default withAuth(
  // `withAuth` augments your `Request` with the user's token.
  function middleware(req) {
    console.log("middleware", req.nextauth.token);
  },
  {
    callbacks: {
      authorized: ({ req, token }) => {
        console.log("authorized "+req.nextUrl.pathname, token);
        if(req.nextUrl.pathname === '/private') {
          return !!token;
        }
        return true;// login or public
      },
    },
    pages: {
      signIn: "/login",
    },
  }
);
export const config = { matcher: ["/private","/public"] };

I linked a reproducer in which I added a description in the README
There are 3 useful pages in the app:

  • /login: a button to sign in (no form)
  • /public: a public url which doesn't need a token to be accessed
  • /private: a private url which requires a token/authentication to be accessed

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.

@Biratus Biratus added the triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. label Aug 17, 2022
@jasontll
Copy link

+1 for this. I am having this issue as well.

@thejessewinton
Copy link

+1 too, Next 12.2.5 didn't solve this problem for me.

@awareness481
Copy link

awareness481 commented Aug 19, 2022

I can reproduce, getting the following error when navigating on the /public & /private routes

[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 NEXTAUTH_SECRET in my .env.local file the error went away and I was able to browse on normally. Can somebody else reproduce that?

Edit2: I'm pretty confident that the combination of NEXTAUTH_SECRET not being defined and the new middleware, is somehow the cause of this error. I can reliably reproduce this error locally without the env & every time I add the secret the error goes away.

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)

@manuel-pchr
Copy link

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.

Environment

Windows
node v16.16.0
Docker version 20.10.17, build 100c701
Docker base image node:16.16.0-alpine3.16
next: ^12.2.5
next-auth: ^4.10.3
react: ^18.2.0

@ilyadoroshin
Copy link

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 SECRET and NEXTAUTH_SECRET. A leftover that was sitting on my api/[...nextauth].ts:

// api/auth/[...nextauth].ts

export default NextAuth({
  secret: process.env.SECRET,

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 NEXTAUTH_SECRET. Urgh, took me ages to figure it out

@ThangHuuVu
Copy link
Member

Hi folks, as @ilyadoroshin and @awareness481 suggested, the problem is missing the NEXTAUTH_SECRET env var
I pulled and check your reproduction @Biratus. Added NEXTAUTH_SECRET and the token is present 👀 I will close this one now.

Output:

authorized /private { sub: '1',
  iat: 1661072808,
  exp: 1663664808,
  jti: '1c4edf6c-4636-4899-8d80-021e83f96994' }
middleware { sub: '1',
  iat: 1661072808,
  exp: 1663664808,
  jti: '1c4edf6c-4636-4899-8d80-021e83f96994' }

P/S: @Biratus For config matcher, you don't need to define both private and public and then check against private. Just don't include public, the middleware will not pick it up.

@ilyadoroshin
Copy link

@ThangHuuVu cool!

Perhaps it would be easier if next-auth is solid on the NEXTAUTH_SECRET?
I would suggest to make it as explicit as possible (secret prop in the NextAuth configuration), so that there's no discrepancies. It also makes perfect sense cuz next-auth is a library

@ghost
Copy link

ghost commented Aug 21, 2022

@ThangHuuVu I scaffolded a new app, have NEXTAUTH_SECRET defined and middleware still won’t work. Next 12.2.5 and NextAuth 4.10.3.

@ilyadoroshin
Copy link

@jrandallw please make sure your NEXTAUTH_SECRET is the same as the secret you're passing to NextAuth({

@ghost
Copy link

ghost commented Aug 21, 2022

I haven’t been passing a secret to NextAuth. The documentation makes it seem as though it will default to using the NEXTAUTH_SECRET env var. Is that not the case? Do I need to pass it into all places?

@ghost
Copy link

ghost commented Aug 21, 2022

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.

@Biratus
Copy link
Author

Biratus commented Aug 21, 2022

@jrandallw I tried it too and still doesn't work.
The .env.local file was ignore by git so it is not here in the reproducer however this is the way I did it but token is still null.
Here is what I tried:

  • NEXTAUTH_SECRET in .env.local, in .env
  • passing it in the NextAuth and middleware options with the secret property: secret: process.env.NEXTAUTH_SECRET
  • Harcoded value for the secret options: secret: "somsecret"
  • Setting NEXTAUTH_SECRET in the system env var: $ export NEXTAUTH_SECRET=...
  • Building the app and running it
    And a combination of all of these.. The token is still null. I really don't know what's wrong.

@sklum
Copy link

sklum commented Aug 21, 2022

I'm having the same problem, have taken all the permutations as @Biratus and others have, and am also still getting a null token.

@ilyadoroshin
Copy link

I really don't know what's wrong.

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

@leandrorlemos
Copy link

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.

try {
// @ts-expect-error
return await _decode({ token, secret })
} catch {
// @ts-expect-error
return null
}
}

The error happens even though the token and secret match the correct values.

@Biratus
Copy link
Author

Biratus commented Aug 22, 2022

I really don't know what's wrong.

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

Yep, tried it too and the value is good. Tried logging the secret value in middleware.js and [...nextauth].js. Both get the right value.
However, and it is weird, vscode doesn't have auto complete for NEXTAUTH_SECRET when I write process.env. Even though it is there, I have auto complete for NEXTAUTH_URL, NODE_ENV, TZ, VERCEL. Only NEXTAUTH_URL is in my .env files
I tried to run the app in the terminal (without vscode) but it stills shows null token.

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 getToken() function. I saw that the secret is good, however the token is an empty string. Here are the variable values in the function (I think it comes from the cookieName, the value in the function is not the same as the one in my browser (secure in browser and not secure in the function, cookieName is generated with secureCookie which is false in my case).

process.env.VERCEL : undefined
secret : '1d4856cff869833b30ac6a9c8c781500ed6faeee4f3c1e8b64cfef88b215e457'

Those value give a secureCookie value of false, which then sets cookieName to 'next-auth.session-token'. In my browser the auth token is set to '__Secure-next-auth.session-token'.
So the function gets a token value of an empty string and then

if (!token) return null

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...
Do tell me if it is a good workaround or not. As I said before, I am new to the framework so maybe this is not the way

@manuel-pchr
Copy link

manuel-pchr commented Aug 22, 2022

The suggested solutions didn't solve my issue.

I´ve tested the token extraction and decoding - this part is working as expected:

Content of .env.development

NEXTAUTH_SECRET=<mySecret>
NEXTAUTH_URL=http://localhost:3000

Content of api/test/token.ts

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 middleware.ts

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:

middleware - withAuth - callbacks - authorized
req:  { cookies: 
   { next-auth.csrf-token: 'next-auth.csrf-token=075c781fb80d9978633789df7d993b962d3cf8321f22946df1c734ef52e213f1%7Cb3bd5577055996d91fee97e794a64d961d8605050a816568f6f33eb51705f4ae; Path=/',
     next-auth.callback-url: 'next-auth.callback-url=http%3A%2F%2Flocalhost%3A3000%2Fadmin; Path=/',
     next-auth.session-token: 'next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..yZVgpIRuDllz0BsI.ix-aZB7x6PijgTMOSmLreLhnwby1Iq5tV_a4qRSkBG0HZBCdE9NFfbBzI1-6t9zIJEugZXm8kby9b5O2bIiv_oiicP5_H1N_ffTm9bpcrGs4hYD6_O48ZZ9s3tDPj1eeZoHLoXSYRHnyfd9KITmuQojdQFy1ddVr9577_L1u1GhdgG2-GTFW0RUu07lcR5-F7JSnGMw0FjiwxKbuBg.aFKDddOE2dAsotEa_eQHDg; Path=/' },
  geo: {},
  ip: undefined,
  nextUrl: 
   { href: 'http://localhost:3000/admin',
     origin: 'http://localhost:3000',
     protocol: 'http:',
     username: '',
     password: '',
     host: 'localhost:3000',
     hostname: 'localhost',
     port: '3000',
     pathname: '/admin',
     search: '',
     searchParams: { [Symbol(impl)]: { _list: [], _url: [Object], [Symbol(wrapper)]: [Circular] } },
     hash: '' },
  url: 'http://localhost:3000/admin',
  bodyUsed: false,
  cache: 'default',
  credentials: 'same-origin',
  destination: '',
  headers: 
   { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
     accept-encoding: 'gzip, deflate, br',
     accept-language: 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6',
     connection: 'keep-alive',
     cookie: 'next-auth.csrf-token=075c781fb80d9978633789df7d993b962d3cf8321f22946df1c734ef52e213f1%7Cb3bd5577055996d91fee97e794a64d961d8605050a816568f6f33eb51705f4ae; next-auth.callback-url=http%3A%2F%2Flocalhost%3A3000%2Fadmin; next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..yZVgpIRuDllz0BsI.ix-aZB7x6PijgTMOSmLreLhnwby1Iq5tV_a4qRSkBG0HZBCdE9NFfbBzI1-6t9zIJEugZXm8kby9b5O2bIiv_oiicP5_H1N_ffTm9bpcrGs4hYD6_O48ZZ9s3tDPj1eeZoHLoXSYRHnyfd9KITmuQojdQFy1ddVr9577_L1u1GhdgG2-GTFW0RUu07lcR5-F7JSnGMw0FjiwxKbuBg.aFKDddOE2dAsotEa_eQHDg',
     host: 'localhost:3000',
     purpose: 'prefetch',
     sec-ch-ua: '"Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"',
     sec-ch-ua-mobile: '?0',
     sec-ch-ua-platform: '"Windows"',
     sec-fetch-dest: 'document',
     sec-fetch-mode: 'navigate',
     sec-fetch-site: 'none',
     sec-fetch-user: '?1',
     upgrade-insecure-requests: '1',
     user-agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' },
  integrity: '',
  keepalive: false,
  method: 'GET',
  mode: 'cors',
  redirect: 'follow',
  referrer: 'about:client',
  referrerPolicy: '',
  signal: { [Symbol(realm)]: { settingsObject: {} } } }
token:  null

The token is always "null".

@sklum
Copy link

sklum commented Aug 26, 2022

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 strategy when using an adapter is "database". However, the Next.js caveats section notes that middleware:

Only supports the "jwt" session strategy. We need to wait until databases at the Edge become mature enough to ensure a fast experience. (If you know of an Edge-compatible database, we would like if you proposed a new Adapter)

I'm not sure if I'm accessing the token correctly now, but after setting session.strategy = "jwt" I have a working login flow. That is, in a new incognito session, if I navigate to localhost:3000/home I'm redirected to my custom signin page in which I can provide an email to send a magic link. Clicking on said link returns me to localhost:3000/home with a correct session object after making it past the middleware because the token is non-null. Without changing the strategy, the token in middleware.ts is always null as mentioned above.

Note that I didn't need to do anything with process.env.NEXTAUTH_SECRET within NextAuthOptions itself. According to the secret docs:

If you set NEXTAUTH_SECRET as an environment variable, you don't have to define this option.

For completeness, here's the relevant code:

[...nextauth.ts]

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);

[middleware.ts]

export { default } from "next-auth/middleware";

export const config = { matcher: ["/home"] };

Getting a null token is pretty opaque with respect to the underlying issue (assuming I'm doing things correctly now). It'd be nice if when attempting to using middleware with the "database" session strategy there was some sort of error / warning instead of failing in this way.

@manuel-pchr
Copy link

I´m also using the PrismaAdapter and have set session.strategy = "jwt", but the token is still null.

Does anyone have further suggestions on how to resolve the(my) issue?

[...nextauth].ts

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);

@afmire877
Copy link

Check your NEXTAUTH_URL is pointing to the deployment URL or your localhost URL . This fixed it for me.

@manuel-pchr
Copy link

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"]
...

@ardinusawan
Copy link

ardinusawan commented Apr 2, 2023

I fix this by getting token from session then decrypt it

npm i jwt-decode
// middleware.ts

import { withAuth } from "next-auth/middleware"
import jwt_decode  from 'jwt-decode';

export default withAuth({
  callbacks: {
    authorized({ req, token }) {
      const tokenValue = req.cookies.get("next-auth.session-token")?.value
      const tokenInSession: any = tokenValue ? jwt_decode(tokenValue || '{}') : new Object()

      // `/admin` requires admin role
      if (req.nextUrl.pathname === "/admin") {
        return token?.userRole === "admin" || tokenInSession?.userRole === "admin"
      }
    
      // `/me` only requires the user to be logged in
      return !!token || !!tokenInSession
    },
  },
})

export const config = { matcher: ["/admin", "/me"] }

@NikkiMather
Copy link

Almost a year old and no solution. What a travesty. I have everything set up correctly, but using withAuth and checking the token of the authorize callback results in null. I could use the request and check for the existence of the token, but that's just not a good 'solution'.

@cris0001
Copy link

I had the same issue. At the beginning try to change your next-auth version.
I'm using next13 and next-auth 4.18.0.

@rharkor
Copy link

rharkor commented Jul 22, 2023

Any updates ?

@PegeDev
Copy link

PegeDev commented Aug 19, 2023

any updates ?

@greg2012201
Copy link

greg2012201 commented Aug 23, 2023

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.

@robFraser1111
Copy link

For me I needed to call getToken otherwise token would return null.

const token = await getToken({
    req,
    secret: process.env.JWT_SECRET_KEY
})

@ronnycoding
Copy link

I have the same issue

@bmsptra24
Copy link

bmsptra24 commented Sep 6, 2023

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:

  • For localhost URL:

    NEXTAUTH_URL=http://localhost:3000
    
  • For deployment URL:

    NEXTAUTH_URL=https://your-vercel-url
    

@jonluca
Copy link

jonluca commented Sep 30, 2023

My issue was that I was using a different cookie name - the config I used to instantiate export default NextAuth(authOptions); used different cookie settings from my middleware.ts file.

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,
});

@goodpilotfish
Copy link

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
NEXTAUTH_URL=mydomain.com

Solution (after also following many tips in this thread):
NEXTAUTH_URL=https://mydomain.com

AHHHHHHHHHHH :S

@freelandm
Copy link

freelandm commented Oct 19, 2023

#5170 (comment)

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.

  • postgres db adapter
  • email (magic link provider)
  • middleware
  • Nextjs 13+ with "app" router

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.

@0xPasho
Copy link

0xPasho commented Jan 16, 2024

Ooof, finally. After so many time trying to fix this, none of the above answers worked for me.
For anyone else like me that keeps getting the null token after all the intents. I needed a quick solution to fix my MVP, so what I did inside the middleware is:

  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 😆.

@SarthakSKumar
Copy link

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.

@Durgaprasad-Budhwani
Copy link

The solution is just to add session: { strategy: 'jwt' }, into the NextAuthOptions

e.g.

NextAuth({
.....
session: { strategy: 'jwt' },
})

You can connect with me on the following social media platforms:

LinkedIn Twitter

Solved 01

@Sotatek-TrungNguyen10
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

Successfully merging a pull request may close this issue.