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

Getting token in middleware doesn't work with authorization header request #4042

Closed
pramodkandel opened this issue Feb 22, 2022 · 30 comments
Closed
Labels
bug Something isn't working good first issue Good issue to take for first time contributors

Comments

@pramodkandel
Copy link

pramodkandel commented Feb 22, 2022

Environment

System:
OS: macOS 11.6
CPU: (4) x64 Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
Memory: 169.62 MB / 16.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 16.13.0 - /usr/local/bin/node
Yarn: 1.22.15 - ~/.yarn/bin/yarn
npm: 8.1.0 - /usr/local/bin/npm
Browsers:
Brave Browser: 72.0.59.35
Chrome: 98.0.4758.102
Edge: 98.0.1108.56
Safari: 14.1.2
npmPackages:
next: ^12.0.7 => 12.0.9
next-auth: ^4.2.1 => 4.2.1
react: 17.0.2 => 17.0.2

Reproduction URL

None

Describe the issue

When using just the authorization header, the getToken(req) method returns correctly in the server with NextApiRequest, but not in the middleware with NextRequest. When I dug through the code, it seems it's because NextApiRequest authorization header is in the list, whereas getToken function is trying to split the authorization header by space, which shouldn't work for this case.

How to reproduce

For any OAuth provider, get the raw token.

Use it from a REST client with the Authorization: Bearer and send an API request.

In the middleware, try getToken({req}). It returns null, whereas in the actual API route code, it returns the token info correctly. Also, it works correctly when a user actually logs in in the browser and there's cookies and other jazz from the browser.

The middleware code is very simple:

import { getToken } from "next-auth/jwt"
import { NextRequest, NextResponse } from "next/server"

export async function middleware(req: NextRequest) {
  const token = await getToken({ req })
  console.log("Middleware token", token)
  NextResponse.next()
}

My API request is:

GET http://localhost:3000/api/<api_path> HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}} 

Expected behavior

I should get the token info in the middleware with just the auth header with bearer token (not only with browser session).

@pramodkandel pramodkandel added the triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. label Feb 22, 2022
@balazsorban44
Copy link
Member

balazsorban44 commented Feb 22, 2022

You should be using the dedicated Middleware API for this now: https://next-auth.js.org/configuration/nextjs#middleware

next-auth/middleware assumes you set the secret as NEXTAUTH_SECRET and will read it for you without passing it anywhere.

Your issue is that you did not pass the secret and next-auth does not know how to decrypt the JWT. Relevant code:

if (!token && req.headers.authorization?.split(" ")[0] === "Bearer") {
const urlEncodedToken = req.headers.authorization.split(" ")[1]
token = decodeURIComponent(urlEncodedToken)
}
// @ts-expect-error
if (!token) return null
// @ts-expect-error
if (raw) return token
try {
// @ts-expect-error
return await _decode({ token, secret })

@pramodkandel
Copy link
Author

pramodkandel commented Feb 22, 2022

I tried this method you recommended as well, but still gives me null. I have not been able to get the token info in middleware by any means so far. Also, I pass secret in the environment variable correctly, and that shouldn't be the issue (unless I need to explicitly pass it somewhere else). Also the reason I can say it's probably not the issue is that it works correctly in the API code (see below description).

Here's my middleware code (pages/api/_middleware.ts). This gives type errors, but that's another issue.:

import { withAuth } from "next-auth/middleware"
import { NextRequest, NextResponse } from "next/server"

export default withAuth(
  function middleware(req: NextRequest & { nextauth: { token: JWT } }) {
    console.log("Middleware token", req.nextauth.token)
    return NextResponse.next()
  },
  {
    callbacks: {
      authorized: ({ token }) => true,
    },
  }
)

Here's my API code (pages/api/<my_api_path>)

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  const token = await getToken({ req })
  console.log("Server side token: ", token)
...
}

My vscode rest client request is this:

GET {{host}}/api/<my_api> HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{secretApiKey}}

The console output is:

Middleware token null
..
..
Server side token:  {
  name: '..',
  email: '...',
  picture: '...',
  sub: '1330646',
  roles: [ 'admin' ],
  iat: 1645512745,
  exp: 1648104745,
  jti: '...'
}

Just for sake of completeness, console logging the token from the authorize callback also results in the same issue, i.e. following middleware code also gives same output:

import { withAuth } from "next-auth/middleware"

export default withAuth({
  callbacks: {
    authorized: ({ token }) => {
      console.log("Middleware token", token)
      return true
    },
  },
})

@pramodkandel pramodkandel changed the title getToken(req) in middleware doesn't work with authorization header request Getting token in middleware doesn't work with authorization header request Feb 23, 2022
@pramodkandel
Copy link
Author

Could you please respond to my comment or reopen the issue? I haven't figured it out yet. Please let me know if I need to provide more info.

@balazsorban44 balazsorban44 reopened this Feb 24, 2022
@balazsorban44
Copy link
Member

Could you attach a minimal reproduction repository? 🙏

Make sure you are on at least 12.0.8 of Next.js (preferably just update to anything that is the current latest), as it had a bug that prevented the env variable from being read correctly before that.

@pramodkandel
Copy link
Author

pramodkandel commented Feb 24, 2022

I forked the next auth example and made a branch and pull request with the above code so you can see it more clearly. This is the pull request: https://github.com/nextauthjs/next-auth-example/pull/67/files

If you fill in all the environment variables, grab the raw token and send API request like I did in rest_client.http file, you'll see "Middleware token" output null whereas token in the API code output correctly. I did this with Google, so filled in Google's ID and secret as well as nextauth secret.

@pramodkandel
Copy link
Author

Any update on this one by any chance? Just an acknowledgement that this is indeed an issue, and that you're prioritizing/deprioritizing this would be helpful to structure my project accordingly.

@balazsorban44
Copy link
Member

Haven't had the time to confirm yet. Kept open with the triage label until then. 👍

If you are certain this is a bug, you are welcome to open a PR. Here is the relevant code: https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/next/middleware.ts

@sidwebworks
Copy link

sidwebworks commented Mar 10, 2022

@balazsorban44 Facing the exact same issue, I am calling my api in the getServerSideProps and my token returns null, I tried everything by reading other similiar issues, but no luck. Only this issue addresses it correctly.

I tried using getSession and getToken, both of them return null for the requests made from getServerSideProps.

It works fine in client side data fetching

I am on a http connection during development, is it possible that could result in nextauth cookies not being sent along with my requests from getServerSideProps?

@sidwebworks
Copy link

sidwebworks commented Mar 10, 2022

Request is made here

export async function getServerSideProps() {
  const client = new QueryClient();

  await client.prefetchQuery("menus", getMenus);

  console.log(client.getQueryData("menus"));

  return {
    props: {
      dehydratedState: dehydrate(client),
    },
  };
}

Middleware

import { getToken } from "next-auth/jwt";
import { NextRequest, NextResponse } from "next/server";

const secret = process.env.NEXTAUTH_SECRET;

async function middleware(req: NextRequest) {

  // @ts-ignore
  const session = await getToken({ req, secret });
  console.log("session: ", session);
  
  if (!req.url.includes("/api/auth") && !session) {
    console.log("session: ********" + req.url, session);
    const response = new NextResponse("Unauthorized", {
      status: 401,
      statusText: "unauthorized",
    });
    return response;
  }

  NextResponse.next();
}

export default middleware;

Note: I tried the nextauth middleware too but it no luck

@lovethisshit
Copy link

lovethisshit commented Mar 14, 2022

Having the same issue. Just a wild guess: "req.headers.authorization" does not work (anymore) as it is used e.g. here:

if (!token && req.headers.authorization?.split(" ")[0] === "Bearer") {
const urlEncodedToken = req.headers.authorization.split(" ")[1]
token = decodeURIComponent(urlEncodedToken)
}
// @ts-expect-error
if (!token) return null
// @ts-expect-error
if (raw) return token
try {
// @ts-expect-error
return await _decode({ token, secret })

What we actually need to do is (or at least that gets me the actual token):
req.headers.get("Authorization")

@balazsorban44 balazsorban44 added bug Something isn't working good first issue Good issue to take for first time contributors and removed triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels May 31, 2022
@balazsorban44
Copy link
Member

balazsorban44 commented May 31, 2022

I believe Next.js Middleware now uses the Headers web API: https://developer.mozilla.org/en-US/docs/Web/API/Headers/get

So we should handle this case correctly. Feel free to open a PR

@balazsorban44
Copy link
Member

Actually, this seems to have been fixed in #4472 already, we just need a new release. See the code here:

const authorizationHeader =
req.headers instanceof Headers
? req.headers.get("authorization")
: req.headers.authorization

@HydroxZ
Copy link

HydroxZ commented Jun 28, 2022

Why is this closed? The original issue has not been resolved yet

@zmeyer44
Copy link

Please reopen this issue! I'm facing the same problem

@rlin415
Copy link

rlin415 commented Jul 25, 2022

I faced the same issue while using next@latest[12.2.3]. Downgrading to next@12.2.0 fixed it for me.

@AlphaBryan
Copy link

I faced the same issue while using next@latest[12.2.3]. Downgrading to next@12.2.0 fixed it for me.

What does your middleware code look now please

@rlin415
Copy link

rlin415 commented Jul 25, 2022

Mine is very simple.

// middleware.js

export { default } from 'next-auth/middleware'

@AlphaBryan
Copy link

AlphaBryan commented Jul 25, 2022

Hmm thanks ! Buts still not working for me even with next@12.2.0 and the middleware in the root
it keep asking for authentification

Update : I comfirm that downgrading to @12.2.0 make all working for now

@90PabloRomero
Copy link

I confirm too, the only way to work is downgrading nextjs... I've been struggling all day, no hope.

@nicosilvabuydepa
Copy link

nicosilvabuydepa commented Jul 26, 2022

I confirm too, the only way to work is downgrading nextjs... I've been struggling all day, no hope.

@90PabloRomero which version of next-auth are you using? I have the same issue even with the v12.0.0

@90PabloRomero
Copy link

90PabloRomero commented Jul 27, 2022

@nicosilvabuydepa sorry for the late reply

"next": "12.2.0",
"next-auth": "4.10.2",

@raphaelpc
Copy link

I confirm too, the only way to work is downgrading nextjs... I've been struggling all day, no hope.

@90PabloRomero which version of next-auth are you using? I have the same issue even with the v12.0.0

I'm also not able to make middleware work properly.
It works ok when i run locally, but dont work if i run on Vercel.
I made a post about this here:
#4969 (comment)

Could all this be related?

@Elvincth
Copy link

same issue using next@latest[12.2.3].

@jeadys
Copy link

jeadys commented Jul 29, 2022

same issue using next@latest[12.2.3].

I downgraded from next 12.2.3 to next 12.2.0 and that fixed the issue for me.

@Elvincth
Copy link

The issue is being moved to: #5008

@jpsaturnino
Copy link

jpsaturnino commented Aug 10, 2022

Also for me, using:

"next": "12.2.0",
"next-auth": "4.10.2",

it works

@lassegit
Copy link

Failed with next@12.2.4. Works with:

"next": "^12.3.0",
"next-auth": "^4.10.3",

@0xPasho
Copy link

0xPasho commented Oct 21, 2023

For anyone working with next version >= 13, this solution worked for me.
You can either set the urls as absolute, or you can create an URL object, both can work, but URL gave me a little bit of issues so I fallbacked to absolutes.

import { NextRequest, NextResponse } from "next/server";
import { getSession } from "next-auth/react";
import { env } from "./env.mjs";

async function middleware(request: NextRequest) {
  const requestForNextAuth = {
    headers: {
      cookie: request.headers.get("cookie"),
    },
  };

  const session = await getSession({ req: requestForNextAuth });

  const isAuthPage =
    request.nextUrl.pathname.startsWith("/login") ||
    request.nextUrl.pathname.startsWith("/register");

  if (!session) {
    let from = request.nextUrl.pathname;
    if (request.nextUrl.search) {
      from += request.nextUrl.search;
    }
    const absoluteFrom = `${env.NEXT_PUBLIC_APP_URL}/${from}`;
    return NextResponse.redirect(
      `${env.NEXT_PUBLIC_APP_URL}/login?from=${encodeURIComponent(
        absoluteFrom,
      )}`,
    );
  } else if (isAuthPage) {
    return NextResponse.redirect(`${env.NEXT_PUBLIC_APP_URL}/dashboard`);
  }
  return null;
}

export default middleware;

export const config = {
  matcher: ["/dashboard", "/org/:path*"],
};

@SarthakSKumar
Copy link

SarthakSKumar commented Jan 17, 2024

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.

@Fesyse
Copy link

Fesyse commented Apr 4, 2024

You are a life saver! Ive been struggling with this problem all day, with no hope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working good first issue Good issue to take for first time contributors
Projects
None yet
Development

No branches or pull requests