Closed KLagdani closed 2 years ago
That will 100% call fetch
with credentials: 'include'
in both cases. Are you 100% sure that the cookies have been set for the host and path you are sending the request to here?
That will 100% call
fetch
withcredentials: 'include'
in both cases. Are you 100% sure that the cookies have been set for the host and path you are sending the request to here?
I am a 100% sure the cookies are set from the server, when I use the exact same host and path with a simple fetch query, the cookies are set correctly on the Application.
Hmm. Then all I could think of would be that you set a breakpoint in fetchBaseQuery
and step through there to see what fetch
is being called with :/
From here I can only say that yes, this should call fetch
with credentials: 'include'
Thank you @phryneas , I'll try that.
Would you say it is better to keep credentials: 'include'
in baseQuery
or per query in each endpoint?
Depends if you want to send those cookies with every request. Usually I would assume you want that, so I'd add it to the baseQuery.
I had the same issue. Turned out that chrome was filtering out the cookies from the request because it was a "cross-site" request and the "secure"-flag on the Cookie was set to false.
So after setting sameSite: "None", secure: true it's now working.
Hi,
i'm facing the same issue: frontend on localhost:3000 and backend on localhost:8080 (CORS configuration ok).
Session cookie is not set in HTTP Request Headers when using RTK Query but no problem using Fetch api :/
I tried setting credentials: 'include' when creating Api
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_API_SERVER_URL }),
credentials: 'include',
prepareHeaders(headers) {
return headers;
},
endpoints: () => ({})
})
and when injecting endpoints
import {apiSlice} from 'api/apiSlice'
export const contentExtendedApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
...
// Mutations
addPost: builder.mutation({
query: ({pageId, payload}) => {
return {
url: `/pages/${pageId}/posts`,
method: 'POST',
credentials: 'include',
body: payload
};
}
}),
}),
})
export const {...} = contentExtendedApiSlice
But failed to have it working as expected like when using fetch api
fetch(' http://localhost:8080/api/pages/819008814840807426/posts', {
method: 'POST',
body: payload,
credentials: 'include',
})
@KLagdani did you solve the issue on your side ? maybe a missing configuration ?
Thanks
That will execute exactly that fetch
call in the background, assuming that the baseUrl
is the same.
@phryneas ha yes indeed, the issue was located between the keyboard and the chair :/
so what's the solution to this? I'm facing the same problem req.cookies always return that ugly [Object: null prototype] {} ...
I had the same issue. Turned out that chrome was filtering out the cookies from the request because it was a "cross-site" request and the "secure"-flag on the Cookie was set to false.
So after setting sameSite: "None", secure: true it's now working.
I tried doing same but it's ignoring. Could you pls share how you did it. Btw I'm using fetch. Pls suggest
@Elendil00 Hi, how did you resolve this?
Hello @Elendil00, any updates on how to resolve this? Thanks!
@arndvs @KLagdani I'm able to solve the issue, The only thing I did is the addition of a proxy to the application.
setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
})
);
};
and change the baseQuery
baseQuery: fetchBaseQuery({
baseUrl: "api",
headers: {
"Content-Type": "application/json",
},
credentials:"include"
}),
I encountered this same issue after deploying my MERN app to render.com. The problem isn't fetchBasedQuery
but the cookies themselves. From this previous reply, When using cross-site cookies in chromium-based browsers, you have to set SameSite: "None"
and Secure: true
.
Also Remember to set the Access-Control-Allow-Credentials
header to true
, and if you're using express js behind a reverse proxy, add app.set('trust proxy', 1);
as well.
If both your applications are on the localhost check that your baseUrl is "127.0.0.1" and not "localhost" (or vice versa I suppose). For me that was what was causing the queries to not include the cookie.
I had this issue, and it took me several hours to resolve and these were my findings.
redux-toolkit
or fetchBaseQuery
,credentials:"include"
in your base query for example
baseQuery: fetchBaseQuery({
baseUrl: "api",
credentials:"include"
}),
and that's all on the frontend side.
credentials
and setup origin
in your CORS
configuration for example with express.
app.use(cors({
credentials: true,
origin: 'http://localhost:3000',
}));
Without setting them you'll get CORS errors.
cookieSession
remember to set signed
to false
, secure
to false
while running locally Hello! I had the same issue and i came with the solution just like so
1- on server side
app.use(cors(
{
origin: "http://localhost:3000/",
credentials: true,
}
));
res.cookie("jwt", token, {
httpOnly: true,
secure: true,
sameSite: 'strict', // Enforce secure cookies & // Prevent CSRF attacks by setting sameSite
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
});
2- in client side
a- apiSlice.js
export const apiSlice = createApi({
baseQuery: fetchBaseQuery({
baseUrl: BASE_URL,
prepareHeaders: (headers) => {
return headers;
},
}),
tagTypes: ["Products", "Orders", "Users"],
endpoints: (builder) => ({}),
});
b- usersApiSlice.js
export const usersApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
registerUser: builder.mutation({
query: (userData) => ({
url: ${USER_URL}/register,
method: "POST",
body: userData,
}),
}),
loginUser: builder.mutation({
query: (userData) => ({
url: ${USER_URL}/login,
method: "POST",
body: userData,
credentials: "include",
}),
}),
logoutUser: builder.mutation({
query: () => ({
url: ${USER_URL}/logout,
method: "POST",
credentials: "include",
}),
}),
}),
}),
I hope that help 😊
I had the same issue. Turned out that chrome was filtering out the cookies from the request because it was a "cross-site" request and the "secure"-flag on the Cookie was set to false.
So after setting sameSite: "None", secure: true it's now working.
this way solved my problem
I am getting a
Cannot POST /api/sessions
My baseQuery:
const baseQuery = fetchBaseQuery({
baseUrl: "api",
credentials: "include",
prepareHeaders: (headers, {getState}) => {
const token = getState().auth.token
if (token) {
headers.set('authorization', `Bearer ${token}`)
}
return headers
}
})
My proxy:
module.exports = function addProxyMiddleware(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'https://<baseurl>:8081/',
changeOrigin: true,
secure: false
})
);
};
My endpoint:
endpoints: builder => ({
login: builder.mutation({
query: credentials => ({
url: '/sessions',
method: 'POST',
body: `{ "UserName": "${credentials.UserName}", "Password": "${credentials.Password}"}`,
headers: {
"Content-type": "application/json; charset=utf-8",}
})
}),
The error that I'm getting is:
{
"status": "PARSING_ERROR",
"originalStatus": 404,
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot POST /api/sessions</pre>\n</body>\n</html>\n",
"error": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON"
}
However, I am able to POST using Postman. What am I missing here?
@morelattes You are getting a HTML response and it tries to parse it as JSON and errors.
@phryneas but the error is a "Cannot POST", which means that my baseurl has some problem right? Or maybe somewhere between the proxy and the RTK query I have written the wrong url?
That's a problem with your server config then, not on the RTK Query side. As long as the outgoing request in your devtools looks right you have to look somewhere else.
Hello people!
I am facing this similar issue now that I have deployed my application using render and was hoping someone could help me.
I deployed my backend express server on render and now I have an issue whereby cookies are not being sent in REQ's made to my backend from my also deployed client side app.
In my client side app using Redux:
apiSlice:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const baseQuery = fetchBaseQuery({
baseUrl: 'https://java-gram-backend.onrender.com/api',
prepareHeaders: (headers) => {
headers.set('credentials', 'include');
return headers;
},
});
export const apiSlice = createApi({
baseQuery,
tagTypes: ['User', 'Post'],
endpoints: (builder) => ({})
});
Backend server:
generateToken:
import jwt from 'jsonwebtoken';
const generateToken = (res, userId) => {
console.log(`NODE_ENV: ${process.env.NODE_ENV}`); // Returns production
const token = jwt.sign({ userId }, process.env.JWT_SECRET, {
// expiresIn: '1h'
});
res.cookie('jwt', token, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
secure: true,
sameSite: 'None',
// maxAge:
});
}
export default generateToken;
app.js:
const corsOptions = {
origin: 'https://java-gram-frontend.onrender.com',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
optionsSuccessStatus: 204,
};
const app = express();
app.use(cors(corsOptions));
authMiddleware.js:
const protect = asyncHandler(async (req, res, next) => {
let token;
console.log(req.cookies);
token = req.cookies.jwt;
console.log(token); // Returns Undefined
if (token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.userId).select('-password');
next();
} catch(error) {
res.status(401);
throw new Error('')
}
} else {
res.status(401);
throw new Error('Unauthorized without a token')
}
});
export { protect };
The endpoints which don't use the authMiddleware (signUp/LogIn) work as expected and successfully generate the token however the token is not stored and sent with subsequent requests thus returning a 401 from all protected endpoints.
I've tried a few different variations to get cookies to work in this scenario and am unsure how to get this to work.
Any help would be greatly appreciated!
@MattHammond94 it could be necessary to set credentials: "include"
in your fetchBaseQuery
config, but apart from that that is really a problem between your browser and your server and doesn't really touch RTK Query. I'd suggest you debug that with a normal fetch
call and leave RTKQ out of the picture until you solved it.
@phryneas Thanks for your advice! I will try adding credentials and failing that I will add a standard fetch call in an attempt to debug.
Pre-deployment when I was working in the dev env locally I did not face this issue. Do you have any theory as to why this is happening now and does the introduction of HTTPS have any effect I am unaware of?
I have tried all the above mentioned examples and nothing seems to work. I tried deploying to aws ec2 and faced the same errors and thought maybe I was configuring nginx the wrong way, I then opted for render.com only for it to work for a few hours and started giving errors of not authorised yet the cookies have been set on the browser.
Here is my server file:
const dotenv = require("dotenv");
const path = require("path");
const connectDB = require("../config/connectDB");
const express = require('express');
let app = express();
connectDB();
// Server Setup
const PORT = process.env.PORT || 5500;
// Middleware
require("./middleware")(app);
//Routes
require("./routes/index")(app);
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
});
// Create route
app.get("/", (req, res) => {
res.send("Server is running");
})
process.on('unhandledRejection', error => {
console.error('unhandledRejection', error)
});
index.js file:
module.exports = (app) => {
const express = require('express');
const cors = require('cors');
const rateLimit = require("express-rate-limit");
const bodyParser = require('body-parser');
const helmet = require("helmet");
const morgan = require("morgan");
const cookieParser = require("cookie-parser");
const errorHandler = require("../middleware/error");
const mongoSanitize = require("express-mongo-sanitize");
const hpp = require("hpp");
const addKey = require("./addKey");
// Express trust proxy settings
app.set('trust proxy', 1)
app.get('/ip', (request, response) => response.send(request.ip))
app.get('/x-forwarded-for', (request, response) => response.send(request.headers['x-forwarded-for']));
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']) // specify multiple subnets as an array
// Add Key Generator Middleware
app.use(addKey);
// Middleware
let limiter = rateLimit({
max: 200,
windowMs: 10 * 60 * 1000,
message: "Too many requests from this IP. Please try again later."
});
app.use('/hin', limiter);
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "same-origin" }));
if (process.env.NODE_ENV !== 'production') {
app.use(morgan('dev'));
}
app.use(bodyParser.json({ limit: "5mb" }));
app.use(bodyParser.urlencoded({
limit: "5mb",
extended: true
}));
app.use(cors({
credentials: true,
origin: ["http://localhost:3000", "https://www.work-orders.online", "https://work-orders.online"],
allowedHeaders: ["Authorization", "Content-Type"],
maxAge: 3600,
methods: ["GET", "POST", "PUT", "DELETE"],
}));
app.use(cookieParser());
app.use(express.json()); // To parse JSON data in the request body
app.use(express.urlencoded({ extended: true })); // To parse form data in the request body
// Prevent HTTP Parameter pollution
app.use(hpp());
// Prevent XSS
app.use(helmet.contentSecurityPolicy());
// Prevent SQL Injection
app.use(mongoSanitize());
// Error Middleware
app.use(errorHandler);
};
protect middleware:
const protect = asyncHandler(async (req, res, next) => {
try {
const token = req.cookies.token;
if (!token) {
return next(new ErrorResponse("Not authorized, invalid token", 401));
}
// Verify token
const verified = jwt.verify(token, process.env.JWT_SECRET);
// Get user id from the token
const user = await User.findById(verified.id);
if (!user) {
return next(new ErrorResponse("User not found, please login", 401));
}
req.user = user;
next();
} catch (error) {
return next(new ErrorResponse("Not authorized, no token", 401));
}
});
Redux rtk configuration files:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const baseQuery = fetchBaseQuery({
baseUrl: "",
credentials: "include",
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set("authorization", `Bearer ${token}`);
}
return headers;
}
});
export const apiSlice = createApi({
baseQuery,
tagTypes: ["Users", "WorkOrder"],
endpoints: (builder) => ({}),
});
import { apiSlice } from "../api/apiSlice";
const serverUrl = import.meta.env.VITE_SERVER_URL
export const authApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation({
query: (data) => ({
url: `${ serverUrl }/login`,
method: "POST",
body: data,
}),
}),
logout: builder.mutation({
query: () => ({
url: `${ serverUrl }/logout`,
method: "POST",
}),
}),
})
});
export const {
useLoginMutation,
useLogoutMutation,
} = authApiSlice;
rtk to get employees:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const serverUrl = import.meta.env.VITE_SERVER_URL;
const baseQuery = fetchBaseQuery({
baseUrl: serverUrl,
credentials: "include",
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
return headers;
},
});
export const employeesApi = createApi({
reducerPath: "employeesApi",
baseQuery,
tagTypes: ["Employee"],
endpoints: (builder) => ({
createEmployee: builder.mutation({
query: (values) => ({
url: `/new/employee`,
method: "POST",
body: values,
}),
invalidatesTags: ["Employee"],
}),
employees: builder.query({
query: (page) => ({
url: `/all/employees?pageNumber=${page}`,
method: "GET",
}),
providesTags: ["Employee"],
}),
singleEmployee: builder.query({
query: (id) => ({
url: `/single/employee/${id}`,
method: "GET",
}),
providesTags: ["Employee"],
}),
employeeData: builder.query({
query: (id) => ({
url: `/employee/data/${id}`,
method: "GET",
}),
providesTags: ["Employee"],
}),
queryAllEmployees: builder.query({
query: () => ({
url: `/query/all/employees`,
method: "GET",
}),
providesTags: ["Employee"],
}),
employeeWorkCount: builder.query({
query: (id) => ({
url: `/employee/work/count?id=${id}`,
method: "GET",
}),
providesTags: ["Employee"],
}),
countAllEmployees: builder.query({
query: () => ({
url: `/count/employees`,
method: "GET",
}),
providesTags: ["Employee"],
}),
editEmployee: builder.mutation({
query: ({id, values}) => ({
url: `/edit/employee/${id}`,
method: "PUT",
body: values,
}),
invalidatesTags: ["Employee"],
}),
deleteEmployee: builder.mutation({
query: (id) => ({
url: `/delete/employee/${id}`,
method: "DELETE",
}),
invalidatesTags: ["Employee"],
}),
}),
});
export const {
useCreateEmployeeMutation,
useEmployeesQuery,
useSingleEmployeeQuery,
useEmployeeDataQuery,
useQueryAllEmployeesQuery,
useEmployeeWorkCountQuery,
useCountAllEmployeesQuery,
useEditEmployeeMutation,
useDeleteEmployeeMutation,
} = employeesApi;
Can anyone help, please
it work for me
`const baseQuery = fetchBaseQuery({
baseUrl: ${process.env.NEXT_PUBLIC_HOST}/api
,
credentials: "include",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
prepareHeaders: (headers) => {
return headers;
},
});`
Hello! I had the same issue and i came with the solution just like so
1- on server side
app.use(cors( { origin: "http://localhost:3000/", credentials: true, } ));
res.cookie("jwt", token, { httpOnly: true, secure: true, sameSite: 'strict', // Enforce secure cookies & // Prevent CSRF attacks by setting sameSite maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days });
2- in client side
a- apiSlice.js
export const apiSlice = createApi({ baseQuery: fetchBaseQuery({ baseUrl: BASE_URL, prepareHeaders: (headers) => { return headers; }, }), tagTypes: ["Products", "Orders", "Users"], endpoints: (builder) => ({}), });
b- usersApiSlice.js
export const usersApiSlice = apiSlice.injectEndpoints({ endpoints: (builder) => ({ registerUser: builder.mutation({ query: (userData) => ({ url: ${USER_URL}/register, method: "POST", body: userData, }), }), loginUser: builder.mutation({ query: (userData) => ({ url: ${USER_URL}/login, method: "POST", body: userData, credentials: "include", }), }), logoutUser: builder.mutation({ query: () => ({ url: ${USER_URL}/logout, method: "POST", credentials: "include", }), }), }), }),
I hope that help 😊
This stupid thing took me 1 day till i saw your post man! I tried credential: "true" and a lot of others things and the yours one " credential: "include" " worked! Many thanks!
https://github.com/reduxjs/redux-toolkit/issues/2095#issuecomment-1569869298 Thanks this helped
Hello,
I have tried setting cookies using RTK Query's fetchBaseQuery with the option credentials: 'include', but it is not working. While doing the same request using fetch does work and sets the cookies like it should.
My server is running on: http://127.0.0.1:4000/api
I am using webpack, and I tried without any proxy and with these 2 proxies, but it won't work in any case:
This is authApi.tsx:
Here is how I use it
Any idea how to set those cookies ?