CORS issue when using cookie in deployed AWS environment - reactjs

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.

Related

CORS error while trying to post data with Axios on AWS REST API configuration using a node.js Lambda function

I'm posting data to a DynamoDB table with axios in a React front-end.
The API is set up through a serverless configuration with an API Gateway and Lambda on AWS.
While the request goes through and I see the added item on the database I still get a CORS error https://i.stack.imgur.com/m7yMG.jpg
This is the axios method:
import axios from "axios";
export const sendItemToDB = async (_data) => {
if (!_data) { return };
try {
const res = await axios({
method: "POST",
url: process.env.REACT_APP_QUERY_API,
data: _data,
headers: {
"Content-Type": "text/plain"
},
});
console.log("data returned from api", res);
} catch (error) {
console.log("Error sending File to db: ");
console.log(error);
}
};
And the API method on Lambda:
const createRecord = async (event) => {
const response = { statusCode: 200 };
try {
const body = JSON.parse(event.body);
const params = {
TableName: process.env.DYNAMODB_TABLE_NAME,
Item: marshall(body || {}),
};
const createResult = await db.send(new PutItemCommand(params));
response.body = JSON.stringify({
message: "Successfully created record.",
createResult,
});
} catch (e) {
console.error(e);
response.statusCode = 500;
response.body = JSON.stringify({
message: "Failed to create record.",
errorMsg: e.message,
errorStack: e.stack,
});
}
return response;
};
I based this configuration on this tutorial : https://github.com/jacksonyuan-yt/dynamodb-crud-api-gateway
I solved this following amazon documentation and reconfiguring the serveless deployment yml.
Serverless documentation on api gateway and lambda proxy integration here
Adding the missing headers to all lambda functions was essential.
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
},
};
Also testing that OPTIONS is working for the preflight:
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-test-cors.html
Just as Stavros noticed, the problem is that this is not a simple cross-origin POST method request (because it contains custom headers), so you need to tweak CORS settings of AWS API Gateway by adding
"POST, GET & OPTIONS" for Access-Control-Allow-Methods
"content-type" for Access-Control-Allow-Headers
You can do it through console like this
You also might need to add those headers in lambda like this
and it will work.

Reactjs Apollo Client Graphql doesn't always attach my refresh cookie in headers

I have a ReactJS application running locally (with https) on port 3000 and a Nest js Graphql lambda server runnning locally (http) on port 4000.
I have have used this article as an example to implement JWT token and cookie (for refresh token) based authentication.
My issue currently is that apollo client in the react application, does not always attach the cookies to the request and that happens randomly. I can see the cookie in the browser storage.
Apollo Client & Http Link set up
this.cache = new InMemoryCache()
this.apolloClient = new ApolloClient({
cache: this.cache,
link: this.authLink.concat(this.httpLink),
connectToDevTools: process.env.REACT_APP_STAGE === 'dev',
credentials: "include",
})
private readonly httpLink = createHttpLink({
uri: `${process.env.REACT_APP_API_URL}`,
credentials: 'include',
fetchOptions: {
credentials: 'include'
}
})
private readonly authLink = setContext((_, { headers }) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: this.accessToken ? `Bearer ${this.accessToken}` : '',
},
}
})
Server Setup:
Graphql options passed to nest js Graphql module
import { GqlModuleOptions, GqlOptionsFactory } from '#nestjs/graphql';
import { Injectable } from '#nestjs/common';
import { join } from 'path';
#Injectable()
export class GraphqlOptions implements GqlOptionsFactory {
createGqlOptions(): Promise<GqlModuleOptions> | GqlModuleOptions {
if (process.env.STAGE === 'dev') {
return {
context: ({ req, res }) => {
return {
req,
res
}
},
autoSchemaFile: 'src/schema.graphql',
playground: {
endpoint: '/graphql',
},
engine: {
reportSchema: true,
},
cors: {
credentials: true,
origin: ["https://localhost:3000"],
}
}
} else {
return {
context: ({ req, res }) => ({ req, res }),
autoSchemaFile: '/tmp/schema.graphql',
playground: {
endpoint: '/graphql',
},
introspection: true
}
}
}
}
When a client requests a token for the first time i provide it in the response header using the bellow code in a mutation resolver:
context["res"].setHeader('Set-Cookie', [refreshToken.cookie])

Express Session Cookie Not Being Set when using React Axios POST Request

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

Using fetch with Express, React, cors, cookies - 'Access-Control-Allow-Origin' header

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

IE 11 not passing Authorization header in API call

Stack : ReactJS, axios , api deployed on AWS
In My reactjs App, I am calling API deployed on aws using axios. This app is working perfectly in chrome and firefox, But failing in IE 11.
All apis are correctly configured to allow Authorization in access control header
I am using below code to add authorization header and accessToken in a request
const axiosInstance = axios.create({
baseURL: `https://abc.api.nonprod.org/`
});
export const createTokenHeaders = (idToken, accessToken) => ({
authorization: `Bearer ${idToken}`,
accessToken: `Bearer ${accessToken}`
});
// Add a request interceptor
export const reqInterceptor = config =>
// get the bearer token and add it in the header
Auth.currentSession()
.then(data => {
const idToken = data.getIdToken().getJwtToken();
const accessToken = data.getAccessToken().getJwtToken();
// eslint-disable-next-line no-param-reassign
config.headers = Object.assign({}, config.headers, createTokenHeaders(idToken, accessToken));
return config;
})
.catch(err => {
console.log(err);
throw new Error('Error getting bearer token');
});
axiosInstance.interceptors.request.use(reqInterceptor);
export const performGet = (uri, queryParams = {}, headers) => {
const requestParams = {
params: queryParams,
headers
};
return axiosInstance.get(uri, requestParams);
};
When I run this app in chrome from localhost:3000 then chrome is correctly calling OPTIONS request and then GET request with correct Authorization header.
But when I am running the same app in IE it is not calling OPTIONS request and also not passing Authorization header in GET request( however it is passing accessToken header).
Is you IE running on enhanced protection mode?
It does not seem to be code issue.
https://www.npmjs.com/package/react-native-axios
react-native-axios support IE11.
use f12 debugger and check for not found url in network traffic.
You probably need to set withCredentials: true in order for authorization headers to be passed to CORS requests.

Resources