The Setup
I have setup a frontend environment using create-react-app. In this environment I use Axios to make a POST request to my Node JS Express Backend Server /login endpoint. I setup sessions middleware using express-sessions and storing the sessions in Redis. I have all this running in localhost currently.
Environment
React App - http://localhost:3005/kp (Note: the service runs on
http://localhost:3005 however, all the routes have /kp in them)
Express Backend - http://localhost:5001
Redis - http://localhost:6379
What Works
When the frontend sends the request to the backend the Express server does it's thing and authenticates the user; it stores the username in the req.session.username (this is just as a test), Redis console shows that the key value store was successful, the frontend network tab shows a 200 status with a Set-Cookie response header and a tab that shows the cookie (screenshots below).
The Problem
Everything seems to be working fine but the cookie is not set in the Browser. I have refreshed the page, tried again many times and yet it will not set the cookie in any browser (Google Chrome & Safari). I am getting frustrated because it seems as though Chrome acknowledges that the Set-Cookie is present but ignores it for some reason.
What I've Tried
Axios
I have tried setting withCredentials: true - Does not work
Verified the cookie with Set-Cookie is being sent back to the frontend after the POST request
Backend
I have checked my CORS policies to but they seem fine; however, I am not great with CORS so there could be misconfiguration there
Tried setting credentials to true in CORS policy
Verified the session with variables are being set with Redis.
Code
React Frontend Axios POST Request
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
}
},
{ withCredentials: true }
)
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
this.setState({
errorMessage: `Server Error`,
loading: false
});
});
Express Server
const express = require('express');
const http = require('http');
const cors = require('cors');
const socketServer = require('./src/sockets');
const bodyParser = require('body-parser');
const session = require('express-session');
const redis = require('redis');
const redisClient = redis.createClient();
const redisStore = require('connect-redis')(session);
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const port = process.env.PORT || 5001;
const { loginRoutes } = require('./src/routers');
const app = express();
redisClient.on('error', (err) => {
console.log('Redis error: ', err);
});
app.use(cors({
origin: '*',
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD'],
credentials: true
}));
// Redis session storage setup
// API Docs for express-session: https://www.npmjs.com/package/express-session
const sessionMiddleware = session({
secret: process.env.REDIS_SECRET || 'testing12345',
name: 'session',
resave: false,
saveUninitialized: true,
cookie: {
secure: false
},
store: new redisStore(
{
host: process.env.REDIS_HOSTNAME,
port: process.env.REDIS_PORT,
client: redisClient,
ttl: 604800
}
)
});
// Uses session middleware
app.use(sessionMiddleware);
app.use(bodyParser.json({ limit: '5mb' }));
const server = http.createServer(app);
// Starts Socket Server
socketServer(server, sessionMiddleware);
// Uses the routes from the router directory
app.use(loginRoutes);
server.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
Screenshots
Network Response
Request Cookie
Application Cookies
As you can see the cookie is missing in the list of browser cookies. I have a feeling it is something small but I have not been able to find anything.
I am not getting any errors in any service. Thank you in advance for your help.
Solution
As Michał Lach pointed out I put withCredentials in the wrong place in the Axios call.
Frontend Axios
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
},
withCredentials: true
})
However, once I did this I began to get CORS error. The CORS error was you cannot have a wildcard '*' in your Access-Control-Allow-Origin (origin) configuration. For this example I changed it to point only to http://localhost:3005; however, there are ways to do dynamic whitelists as documented here: https://www.npmjs.com/package/cors#configuring-cors-w-dynamic-origin
Backend
app.use(cors({
origin: 'http://localhost:3005',
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD'],
credentials: true
}));
Once I made these changes the cookie started being set on the frontend correctly.
Are you sure, you are setting axios options correctly ?
You have :
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
}
},
{ withCredentials: true }
)
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
this.setState({
errorMessage: `Server Error`,
loading: false
});
});
Try this:
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
},
{ withCredentials: true }
}
....
Related
I am currently working on a project using Ionic React with .NET API for login process.
And this is the problem I am facing:
This is the Response Header of my post request
And when I try to look at the application cookie, it seems that it is not being set.
This is the application view which should see the jwt cookie has been stored.
I have already added these two params for my Axios post request but not work at all.
{withCredentials: true, credentials: 'include'}
Thank you for your time to look into my question or help!!!
Below are my request/response setting on the client-side and backend:
Axios request:
const api = axios.create({
baseURL: `https://localhost:7220/api/User`
})
api.post("/login", postData, {withCredentials: true, credentials: 'include'})
.then(res => {
console.log(res);
})
.catch(error=>{
console.log(error);
})
Controller:
Response.Cookies.Append(key: "jwt", value: token, new CookieOptions
{
HttpOnly = true
});
Response response = new Response { Status = true, Message = _LOGINSUCCESSFUL };
return Ok(response);
Program.cs:
builder.Services.AddCors(p => p.AddPolicy("corsapp", builder =>
{
builder.WithOrigins("http://localhost:3000").AllowCredentials().AllowAnyMethod().AllowAnyHeader().AllowAnyMethod();
}
)
);
Having a nodejs server using Nestjs, Express, and GraphQL I configure the server with the below.
GraphqlOptions.ts
#Injectable()
export class GraphqlOptions implements GqlOptionsFactory {
createGqlOptions(): Promise<GqlModuleOptions> | GqlModuleOptions {
return {
context: ({ req, res }) => ({ req, res }),
autoSchemaFile: '/tmp/schema.graphql',
playground: {
endpoint: '/graphql',
},
introspection: true,
cors: {
// Read that we should do this so GraphQL will not override the Express CORS configuration
},
}
}
}
}
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule)
await app.listen(3000)
}
bootstrap()
index.ts
let cachedServer: Server
const bootstrapServer = async (): Promise<Server> => {
const expressApp = express()
expressApp.use(eventContext())
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressApp)
)
app.useGlobalPipes(new ValidationPipe())
const corsOptions = {
credentials: true,
origin: [`${process.env.WEB_APP_URL}`],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS'
}
app.enableCors(corsOptions)
app.use(cookieParser())
app.use(helmet())
await app.init()
return createServer(expressApp)
}
export const handler: APIGatewayProxyHandler = async (event, context) => {
if (!cachedServer) {
cachedServer = await bootstrapServer()
}
return proxy(cachedServer, event, context, 'PROMISE').promise
}
And in the Reactjs app configuring Apollo Client with the below.
private readonly httpLink = createHttpLink({
uri: 'https://theserver.yyy',
credentials: 'include',
fetch,
fetchOptions: {
credentials: 'include'
}
})
private readonly authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: this.accessToken ? `Bearer ${this.accessToken}` : '',
},
}
})
this.apolloClient = new ApolloClient({
cache: this.cache,
link: this.authLink.concat(this.httpLink),
connectToDevTools: true,
credentials: 'include',
})
When running the server (localhost:4000) and the Reactjs app (localhost:3000) locally everything works fine but technically both are from the same origin (localhost) where when apps deployed server is (theserver.yyy) domain and reactjs (thewebap.ddd) domain in result receiving the below in Chrome browser.
Access to fetch at 'https://theserver.yyy/graphql' from origin 'https://thewebap.ddd' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.
And similar using Firefox.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://theserver.yyy/graphql. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
CORS is enabled in AWS API Gateway. I'd appreciate some direction to learn how to allow CORS origin from my webapp to the server and learn about CORS in general. Specifically, any hints configuring nestjs GraphQL is appreciated.
The solution is you need to pass CORS options in GraphqlOptions and not in Express configuration.
spent a lot of time trying to figure this out with no success. I'm trying to get csurf protection running with cookies. At the moment I've simplified the following code as much as I can. I'm running a fetch request from React to another server on Express. It gives the error:
"Access to fetch at 'http://localhost:3003/testlogin' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled."
As I understand, the 'Access-Control-Allow-Origin' header is set in my corsOptions. So it shouldn't throw this error. I'm clearly missing something. Any help gratefully received.
Express server:
const cookieParser = require("cookie-parser");
const cors = require('cors')
const express = require("express");
const csrf = require("csurf");
const app = express();
const csrfMiddleware = csrf({ cookie: true });
app.use(cookieParser());
const corsOptions = {
origin: 'http://localhost:3000',
methods: "GET,HEAD,POST,PATCH,DELETE,OPTIONS",
credentials: true,
allowedHeaders: "Content-Type, Authorization, X-Requested-With, Accept",
}
app.options('*', cors(corsOptions))
app.use(csrfMiddleware);
app.post("/testlogin", cors(corsOptions), (req, res) => {
console.log('test login reached', );
res.end(JSON.stringify({ status: "success" }));
});
const PORT = process.env.PORT || 3003;
app.listen(PORT, () => {
console.log(`Listening on http://localhost:${PORT}`);
});
Front-end (react):
function testClick() {
console.log("testClick Clicked");
let urlToGetUserProfile = 'http://localhost:3003/testlogin'
return fetch(urlToGetUserProfile, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
credentials: "include",
body: JSON.stringify({ idToken: "idTokengoeshere" }),
})
.then((fetchResponse) => {
console.log(fetchResponse);
})
}
The csrfMiddleware middleware function gets called before the cors middleware.
It throws ForbiddenError: invalid csrf token which stops the cors middleware from adding headers to the response.
You can resolve that by putting the cors middleware first.
app.use(cors(corsOptions));
app.use(csrfMiddleware);
app.post("/testlogin",(req, res) => {
console.log('test login reached', );
res.end(JSON.stringify({ status: "success" }));
});
I've been trying to get my frontend and backend to share cookies but the server never actually get them.
my frontend has credentials include on it
const res = await fetch('http://localhost:5000/v1/auth/register', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
and my backend have my core like
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true,
})
);
and they still don't send cookies!
even tho I have a route that sets the cookie on the frontend from the backend
res.cookie('auth-token', token, {
httpOnly: true,
maxAge: 86400,
});
and it works perfectly fine after I used cors.
my check auth middleware:
exports.verifyAuth = async (req, res, next) => {
try {
const token = req.cookies['auth-token'];
if (!token) {
return res.status(401).json({ msg: 'No token, authorization required.' });
}
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
req.user = decodedToken.user;
next();
} catch (err) {
res.status(401).json({ msg: 'No token, authorization required.' });
}
};
EDIT:
I was debugging it and apparently its always the first ever time that it doesn't send the cookies and that's because I'm using getInitialProps to send the request but I'm guessing that the request happens before cookies are loaded so I'm trying to find a way to pass the cookies to the request manually because fetch doesnt.
Try this config: {withCredentials: true}
I want to make a POST request from a React app using Axios to a Django Rest Framework backend. I have managed to get a CSRF Token from the backend but I can't manage to send it with my request, so I always get a Forbidden (CSRF cookie not set.) error:
This is the code of my React app:
handleClick() {
const axios = require('axios');
var csrfCookie = Cookies.get('XSRF-TOKEN');
console.log(csrfCookie)
axios.post('http://127.0.0.1:8000/es/api-auth/login/',
{
next: '/',
username: 'admin#admin.com',
password: 'Cancun10!',
},
{
headers: {
'x-xsrf-token': csrfCookie, // <------- Is this the right way to send the cookie?
},
withCredentials = true,
}
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
}
And this is my settings.py CSRF configuration:
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_HEADERS = (
'xsrfheadername',
'xsrfcookiename',
'content-type',
'XSRF-TOKEN',
)
CORS_ORIGIN_WHITELIST = serverconfig.CORS_ORIGIN_WHITELIST
CSRF_TRUSTED_ORIGINS = serverconfig.CSRF_TRUSTED_ORIGINS
CSRF_COOKIE_NAME = "XSRF-TOKEN"
Django uses X-CSRFTOKEN as the csrf header by default, see here. The option CSRF_COOKIE_NAME you use in your Django settings only changes the cookie name, which by default is csrftoken, see here.
To solve your issue, use this header in your axios call: headers: { 'X-CSRFTOKEN': csrfCookie }.
Use the following:
axios.post('http://127.0.0.1:8000/es/api-auth/login/',
{
next: '/',
username: 'admin#admin.com',
password: 'Cancun10!',
},
{
headers: {
'X-CSRFTOKEN': csrfCookie,
},
},
)
Also, remove XSRF-TOKEN from CORS_ALLOW_HEADERS in your Django settings, and add X-CSRFTOKEN to it instead. If you don't feel like removing XSRF-TOKEN, you can safely add X-CSRFTOKEN to CORS_ALLOW_HEADERS with the following, documentation here
# settings.py
from corsheaders.defaults import default_headers
CORS_ALLOW_HEADERS = list(default_headers) + [
'X-CSRFTOKEN',
]
Also, it's will be easier if you create an Axios instance
const instance = axios.create({
baseURL: API_URL,
withCredentials: true,
xsrfHeaderName: 'X-CSRFToken',
xsrfCookieName: 'csrftoken',
})
And make sure xsrfCookieName and CSRF_COOKIE_NAME have the same name. Note that if CSRF_COOKIE_HTTPONLY set to True, client-side JavaScript will not be able to access the CSRF cookie:
# settings.py
CSRF_COOKIE_NAME = "csrftoken"
CSRF_COOKIE_HTTPONLY = False
CORS_EXPOSE_HEADERS = ["Content-Type", "X-CSRFToken"]
CORS_ALLOW_CREDENTIALS = True