nextauthjs / next-auth

Authentication for the Web.
https://authjs.dev
ISC License
24.8k stars 3.49k forks source link

Session not received as authenticated during a GET request #4238

Open formerskulk4 opened 2 years ago

formerskulk4 commented 2 years ago

Provider type

Credentials

Environment

System: OS: Windows 10 10.0.19044 CPU: (4) x64 Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz Memory: 5.06 GB / 15.87 GB Binaries: Node: 14.17.5 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.17 - ~\AppData\Roaming\npm\yarn.CMD npm: 8.1.3 - C:\Program Files\nodejs\npm.CMD Browsers: Edge: Spartan (44.19041.1266.0), Chromium (99.0.1150.46) Internet Explorer: 11.0.19041.1566 npmPackages: next: ^12.0.10 => 12.0.10 next-auth: ^4.2.1 => 4.2.1 react: 17.0.2 => 17.0.2

Reproduction URL

https://github.com/ParidV/Dua-n-pu

Describe the issue

getSession returning null in Get requests, in POST request the session is okay.

Component

`export async function getServerSideProps(context) {
  const res = await axios.get(`${process.env.API_URL}/company/jobs`);
  return {
    props: {
      jobs: res.data.jobs,
    },
  };
}`

API Request api/company/jobs/index.js

import { getSession } from "next-auth/react";

export default async (req, res) => {
  const { method } = req;
  switch (method) {
    case "GET":
      try {
        const session = await getSession({ req });

        console.log(session + " SESSION "); //RETURN NULL

        console.log(JSON.stringify(session));
        const jobs = await prisma.jobs.findMany({
          where:{
            userId: session.id
          }
        });
        return res.status(200).json({ success: true, jobs });
      } catch (error) {
        return res.status(404).json({
          success: false,
          message: error.message,
        });
      }

How to reproduce

In POST request, the session is okay, but when I make a get request to get the ID from the server to pass in the axios request come as NULL.

Expected behavior

It should return the session

ndom91 commented 2 years ago

I've setup a quick reproduction based on your API route code provided above and it seems to work for me. It will return null if you're unauthenticated. However, if you copy a request "as curl" out of your browsers network dev tools (when authenticated) which includes the token/cookie and modify the URL to the api route, I got a response and the dev server terminal logged the session successfully.

Can you double check the GET requests you're making are actually also authenticated (i.e. including the cookie or authorization header?

formerskulk4 commented 2 years ago

I can confirm that with the GET request, authenticated does not receive the session. I tried with POSTon This request and it works perfectly. I created a job from the POST request and I received this by console.log(session)

{
  user: { name: 'Company', email: 'company@email.com' },
  expires: '2022-04-22T19:23:39.673Z',
  id: 2,
  name: 'Company',
  surname: 'Surname',
  email: 'company@email.com',
  role: 2
}

I tried a POST request from postman and it says that I am not logged in, which is okay, but when I am authenticated from my project, the session is retrieved successfully. So this problem happens only with GET

This is the request from cuRL from the get request:

curl "http://localhost:3000/company/jobs" ^
  -H "Connection: keep-alive" ^
  -H "sec-ch-ua: ^\^" Not A;Brand^\^";v=^\^"99^\^", ^\^"Chromium^\^";v=^\^"99^\^", ^\^"Google Chrome^\^";v=^\^"99^\^"" ^
  -H "sec-ch-ua-mobile: ?0" ^
  -H "sec-ch-ua-platform: ^\^"Windows^\^"" ^
  -H "DNT: 1" ^
  -H "Upgrade-Insecure-Requests: 1" ^
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36" ^
  -H "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" ^
  -H "Sec-Fetch-Site: none" ^
  -H "Sec-Fetch-Mode: navigate" ^
  -H "Sec-Fetch-User: ?1" ^
  -H "Sec-Fetch-Dest: document" ^
  -H "Accept-Language: en-US,en;q=0.9,de-DE;q=0.8,de;q=0.7,sq;q=0.6" ^
  -H "Cookie: _ga=GA1.1.622907214.1643984174; next-auth.csrf-token=c2db1395200a91b1915039666fe73b4d36a5fd49a430dffac15d0d704b6613dc^%^7C07336a24d0a4cc0a2dd92548d698de66d84e5f3daf1e28b5592e3a2cb9ee871f; next-auth.callback-url=http^%^3A^%^2F^%^2Flocalhost^%^3A3000^%^2F; next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..k5rKQz-UT6gGL2O8.381fW_sK8GZ-zo8PA3gS1V7xmwBvpYMc_EkgNx9wcbxKh9KsHGVe1adOrIUfexJeoxpP_WpBc1rpp3BIUdlPcLPCbmgor_WjHLbRhmOLQd7mW2bZN8C7DyZm3JlUdBCX6iIpOicYeNJSI6dtooVQkC136X1izbyaP3E92z56CiKbEhLVLd0o-SZZItreAvWXD04RjlqAhAmqovvwrcB-A1POYDxfsw2rR3Vf.UQuUbRZhvGPXwDV8nJB7Zg" ^
  --compressed
ndom91 commented 2 years ago

Okay, alternatively you can give our experimental getServerSession a try. Something like this:

import { getServerSession } from "next-auth/next";
import { authOptions } from './auth/[...nextauth]'

export default async (req, res) => {
  const { method } = req;
  switch (method) {
    case "GET":
      try {
        const session = await getServerSession({req, res}, authOptions)
        console.log("SESSION", session)

        res.status(200).json({
          message: "Success",
          session
        })
      } catch (error) {
        console.error(error);
        res.status(500).json({ error: error.message });
      }
  }
}

I was also able to get this ^^ to successfully work.

We've released it as a preview in the latest release, and there are two open PRs which provide more documentation for it which you can take a look at:

formerskulk4 commented 2 years ago

Thank you for your reply, but what is authOptions because referring to my [...nextauth.js] and I do not have a authOptions, in this way can you double check the procedure if the authentication is done correctly (for security issues or any other problem such as GET request, even though I think has nothing to do with [..nextauth].js )

import NextAuth from "next-auth";
import CredentialProvider from "next-auth/providers/credentials";
import axios from "axios";

export default NextAuth({
  providers: [
    CredentialProvider({
      name: "credentials",
      async authorize(credentials) {
        try {
          const user = await axios.post(
            `${process.env.API_URL}/auth/authentication/login`,
            {
              email: credentials.email,
              password: credentials.password,
            }
          );

          if (!user.data.user) {
            return null;
          }

          if (user.data.user) {
            return {
              id: user.data.user.id,
              name: user.data.user.name,
              surname: user.data.user.surname,
              email: user.data.user.email,
              role: user.data.user.role,
            };
          }
        } catch (error) {
          console.error(error);
        }
      },
    }),
  ],
  callbacks: {
    jwt: ({ token, user }) => {
      if (user) {
        token.id = user.id;
        token.name = user.name;
        token.surname = user.surname;
        token.email = user.email;
        token.role = user.role;
      }
      // Here, check the token validity date
      if (token.tokenExpiration < Date.now()) {
        // Call the endpoint where you handle the token refresh for a user
        const user =  axios.post(
          `${process.env.API_URL}/auth/authentication/refresh`,
          {
            refreshToken: token.refreshToken,
          }
        );
        // Check for the result and update the data accordingly
        return { ...token, ...user };
      }
      return token;
    },
    session: ({ session, token }) => {
      if (token) {
        session.id = token.id;
        session.name = token.name;
        session.surname = token.surname;
        session.email = token.email;
        session.role = token.role;
      }
      return session;
    },
  },
  secret: process.env.SECRET_KEY,
  jwt: {
    secret: process.env.SECRET_KEY,
    encryption: true,
    maxAge: 5 * 60 * 1000,
  },
  pages: {
    signIn: "/auth/login",
  },
});
formerskulk4 commented 2 years ago

I also gave a try with getServerSession, but still no success.

API request

        const session = await getServerSession({ req, res }, authOptions);
        console.log(session);

        console.log("SESSION", session);

        if (!session) {
          return res.status(401).json({
            message: "You are not logged in",
          });
        }

[..nextauth].js

import NextAuth from "next-auth";
import CredentialProvider from "next-auth/providers/credentials";
import axios from "axios";

export const authOptions = {
  providers: [
    CredentialProvider({
      name: "credentials",
      async authorize(credentials) {
        try {
          const user = await axios.post(
            `${process.env.API_URL}/auth/authentication/login`,
            {
              email: credentials.email,
              password: credentials.password,
            }
          );

          if (!user.data.user) {
            return null;
          }

          if (user.data.user) {
            return {
              id: user.data.user.id,
              name: user.data.user.name,
              surname: user.data.user.surname,
              email: user.data.user.email,
              role: user.data.user.role,
            };
          }
        } catch (error) {
          console.error(error);
        }
      },
    }),
  ],
  callbacks: {
    jwt: ({ token, user }) => {
      if (user) {
        token.id = user.id;
        token.name = user.name;
        token.surname = user.surname;
        token.email = user.email;
        token.role = user.role;
      }
      // Here, check the token validity date
      if (token.tokenExpiration < Date.now()) {
        // Call the endpoint where you handle the token refresh for a user
        const user = axios.post(
          `${process.env.API_URL}/auth/authentication/refresh`,
          {
            refreshToken: token.refreshToken,
          }
        );
        // Check for the result and update the data accordingly
        return { ...token, ...user };
      }
      return token;
    },
    session: ({ session, token }) => {
      if (token) {
        session.id = token.id;
        session.name = token.name;
        session.surname = token.surname;
        session.email = token.email;
        session.role = token.role;
      }
      return session;
    },
  },
  secret: process.env.SECRET_KEY,
  jwt: {
    secret: process.env.SECRET_KEY,
    encryption: true,
    maxAge: 5 * 60 * 1000,
  },
  pages: {
    signIn: "/auth/login",
  },
};

export default NextAuth(authOptions);
formerskulk4 commented 2 years ago

I have created a new project to test that and it is still with the same problem,

  1. First I have authenticated
  2. Visited http://localhost:3000/api/hello
  3. Still the session was null

Image

ubbe-xyz commented 2 years ago

@formerskulk4 getSession() or getServerSession() will return you null if the user is not authenticated. Have you verified that the user is logged in when calling the function?

ParidV commented 2 years ago

I am positive that the user is authenticated, it is weird because when POST request is used there is no null returned.

markmendozadev commented 2 years ago

Hey im having this problem as well the user is authenticated

i'm seeing a next-auth.session-token on cookies as well but when i try to request GET on getServerSideProps it will return me null on getSession on Handler(/api)

but if i use client side fetching it works perfectly

my code

/api/tasks/index.js

const handler = async (req, res) => {
    if (req.method === "GET") {
     const session = await getSession({ req });

    if (!session) {
      res.status(401).json({ message: "Not Authenticated" });
      return;
    }
    const userId = session.user.id;
    const client = await connectDb();
    const db = client.db();
    const tasks = await db
      .collection("tasks")
      .find({ user_id: userId })
      .toArray();
    res.status(200).json(tasks);
  }
};

index.js

export const getServerSideProps = async (context) => {

  const res = await fetch(`http://localhost:3000/api/tasks`);
  const data = await res.json();
  return {
    props: { data },
  };
};

where i fetch here now i get a message: "Not Authenticated" (i'm logged in 100% sure)

useEffect works perfectly fine(client fetching)

  useEffect(() => {
    const fetchData = async () => {
      const res = await fetch(`http://localhost:3000/api/tasks`);
      const data = await res.json();
      console.log(data);
    };
    fetchData();
  }, []);
ParidV commented 2 years ago

@markmendozadev

When you use the POST request does it work? Because I am having the same issue, I am positive that the user is authenticated however here returns null on the server side.

jamiviz commented 2 years ago

use this code in your _app.js :

function MyApp({Component, pageProps}) { return ( <SessionProvider session={pageProps.session}> <Component {...pageProps} /> </SessionProvider> ); }

ParidV commented 2 years ago

@jaminecode I have already that.

`export default function App({ Component, pageProps }) { return (

); } `

markmendozadev commented 2 years ago

Hi @ParidV Yes i think i found my answers already but not 100% sure. It works on client-side because getSession works on client-side requests.

if you do a request on your api through client side (not using any next data fetching just pure react it will 100% work) i mean the authentication.

now i did a little digging and here's what i found (https://github.com/nextauthjs/next-auth/pull/4116)

@jaminecode my _app.js already like that

import "../styles/globals.css";
import { SessionProvider } from "next-auth/react";
import Layout from "../components/layout/Layout";

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </SessionProvider>
  );
}
export default MyApp;
luismbcr commented 2 years ago

@markmendozadev maybe this is a late response, but I faced the same issue with POST method, I could access the session, but not with POST, after doing some research, I found this is because NextJS does not send the cookies in the header for GET method, so I fixed the issue like this:

image

`let res = await fetch("http://localhost:3000/api/posts", { method: "GET", headers: {

  "Content-Type": "application/json",
  // I have to add cookie in the GET 
  Cookie: context.req.headers.cookie,
},

});`

EperezOk commented 1 year ago

@markmendozadev maybe this is a late response, but I faced the same issue with POST method, I could access the session, but not with POST, after doing some research, I found this is because NextJS does not send the cookies in the header for GET method, so I fixed the issue like this:

image

That worked for me, thanks!

stanko-tomic commented 1 year ago

@markmendozadev maybe this is a late response, but I faced the same issue with POST method, I could access the session, but not with POST, after doing some research, I found this is because NextJS does not send the cookies in the header for GET method, so I fixed the issue like this:

image

`let res = await fetch("http://localhost:3000/api/posts", { method: "GET", headers: {

  "Content-Type": "application/json",
  // I have to add cookie in the GET 
  Cookie: context.req.headers.cookie,
},

});`

Still having an issue with using POST request where when using POST session returns null, but when using GET it returns normally (client-side)

The console log i would get would be

[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error undefined {
  error: {},
  url: 'http://localhost:3000/api/auth/session',
  message: undefined
}

But even though the link works and have nextauth url in .env and because it doesnt error out when using "GET"

minhajul-islam commented 1 year ago

{

the same issue, have solved it?

talon2295 commented 1 year ago

I had the same problem, when I try to make a GET request everything works fine, but when I make a POST request, it seems that the user is not logged in.

I solved it this way

const body = {...req.body} ;
req.body = null ;
const session = await getSession({ req:req });
req.body = body ;

I am not sure, but I think there is a problem inside the fetchData function in file node_modules/next-auth/src/client/_utils.ts image

paschalidi commented 1 year ago

Having the same issue on "next": "^14.0.1" and "next-auth": "4.24.4"

When I am fetching from the API routes (serverless functions) I am cant retrieve the session. But this is only when I am fetching from the nextjs app.

Let me explain: I have this API endpoint on my /api/session.

// api/session/route.ts

import { getServerSession } from "next-auth";
import { NextResponse } from "next/server";
import { authOptions } from "@/lib/authOptions";

export async function GET(request: Request) {
  const session = await getServerSession(authOptions);

  return NextResponse.json({
    authenticated: !!session,
    session,
  });
}

// page.tsx
import { authOptions } from "@/lib/authOptions";
import { getServerSession } from "next-auth";

async function getSession() {
  try {
    const response = await fetch(
      `${process.env.NEXT_PUBLIC_API_URL}/api/session`,
      {
        method: "GET", 
        headers: { "Content-Type": "application/json" },
        cache: "no-cache",
      },
    );

    if (!response.ok) {
      throw new Error("Network response was not ok");
    }

    return response.json(); 
  } catch (error) {
    console.log(error);
  }
}

const Page = async () => {
  const firstSession = await getSession();
  const secondSession = await getServerSession(authOptions);
  return (
    <pre>
      {JSON.stringify(firstSession, null, 2)}
      {JSON.stringify(secondSession, null, 2)}
    </pre>
  );
};
export default Page;

The result of this is the following

image

However when I reach for the same endpoint through the browser I am not seeing the following

image

Seems like the fetch is missing some headers/cookies. Anyone has any suggestions on what I am missing ?

paschalidi commented 1 year ago

Okay I think I got it now

The reason I was getting null when I called getServerSession() from server-side component is because cookies are not passed by default in server-side fetch.

So as people here mentioned before you have to pass the headers.

    const response = await fetch(
      `${process.env.NEXT_PUBLIC_API_URL}/api/reservations/list`,
      {
        method: "GET",
        headers: headers(), // passing the headers :) 
        cache: "no-cache",
      },
    );
williamisnotdefined commented 6 months ago

Guys.. I hope to help you all, but I'm not sure.. My need is to make a request at getServerSideProps by using axios, but ofc the session will be not there in the axios interceptor when the request gets fired, so I made a function to attach the session into my axios instance.

Next 14.2 Next-Autg v5 (beta 17 ATM)

in my specific case the following solution has been working so far:

// the file where the getServerSideProps is

import { GetServerSideProps } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";

import getServerSession from "@/core/axios/getServerSession"; // <<-- this is the guy!
import getAvailableSites from "../path/to/getAvailableSites";

export const getServerSideProps: GetServerSideProps = async ({
  locale,
  req,
  res,
}) => {
  try {
    await getServerSession(req, res);
    const availableSites = await getAvailableSites();

    // ... bla bla bla
    return { props: {/* what you need to return*/} };
};
// getServerSession.ts

import type { IncomingMessage, ServerResponse } from "http";
import { NextApiRequest, NextApiResponse } from "next/types";

// default auth function from next-auth v5
import { auth } from "@/auth";

import { axiosInstance } from ".";

const getServerSession = async (req: IncomingMessage, res: ServerResponse) => {
  const session = await auth(req as NextApiRequest, res as NextApiResponse);
  if (session) {
    axiosInstance.defaults.headers.common.Authorization = `Bearer ${session.accessToken}`;
    return;
  }

  throw new Error("getServerSession - Not authenticated.");
};

export default getServerSession;

please guys, let me know if I'm missing something.. I'm sure there is a better way..

Good luck guys!

turkus commented 1 month ago

Okay I think I got it now

The reason I was getting null when I called getServerSession() from server-side component is because cookies are not passed by default in server-side fetch.

So as people here mentioned before you have to pass the headers.

    const response = await fetch(
      `${process.env.NEXT_PUBLIC_API_URL}/api/reservations/list`,
      {
        method: "GET",
        headers: headers(), // passing the headers :) 
        cache: "no-cache",
      },
    );

@paschalidi you saved my day, thanks! method: "GET" is not necessary and remember about to wrap it when using Vercel in: headers: new Headers(headers()),