http-proxy to cloudfront results in 403 error - proxy

I have a series of serverless next.js apps running on AWS that I am serving at subdomains, and I want to proxy them to subdirectories on my main domain. So, for example, the app at foo.example.com/foo/ should appear at www.example.com/foo/.
I've accomplished this by using http-proxy and express. I have a fairly simple express server that runs in its own serverless app, like so:
const serverless = require('serverless-http');
const httpProxy = require('http-proxy');
const express = require('express');
const app = express();
const proxy = httpProxy.createProxyServer();
app.get('/', (req, res) => {
res.send('Hello, you are at index.');
});
app.get('/:project/*', (req, res) => {
const project = req.params.project;
const rest = req.params[0];
const url = `https://${project}.example.com/${project}/`;
req.url = `/${rest}`;
req.headers['X-Projects-Router-Proxy'] = true;
req.body = undefined;
proxy.web(req, res, {
target: url,
xfwd: false,
toProxy: true,
changeOrigin: true,
secure: true,
});
});
module.exports.handler = serverless(app);
This works quite well on its own, which is great. However, when I try to put this behind CloudFront, the index page works fine, but anything that touches the proxy returns a 403 error:
403 ERROR
The request could not be satisfied.
Bad request.
Generated by cloudfront (CloudFront)
What might be going wrong here, and how can I configure http-proxy so that it will cooperate with CloudFront?

You need to add *.example.com into CloudFront CNAME/Alternative name filed and also in DNS to point it to CloudFront, when url changes as {project}.example.com, CloudFront finds the distribution based on the host header and if it can't find, it'll give you 403 error.

Related

How to integrate AWS API Gateway API Key with Axios | CORS Error

I have built an API in AWS API Gateway. I have written the endpoints to perform basic CRUD operations as well. I am making a call to those endpoints using axios from my React frontend. The APIs in turn call AWS Lambda functions to interact with DynamoDB.
Since DynamoDB contains sensitive user data, I wish to secure it with an API key.
As per the steps mentioned here and here.
Now in order to make an API call I had the following code. Please note that I have swapped in the real values with dummy values for explanation purposes.
src/config/api.js
const customHeaders = {
"X-Api-Key": "thisIsADummyStringForExplanation",
"Content-Type": "application/json",
};
const axiosInstance = axios.create({
baseURL: "https://this.is.a.dummy.base.url/v0",
headers: customHeaders,
});
const Aws_Api_Gateway_GET = (uri) => {
return axiosInstance({
method: "get",
url: `${uri}`,
timeout: 2000,
});
};
export { Aws_Api_Gateway_GET };
Following is the Code that I wrote in order to make a GET request at the API endpoint
Aws_Api_Gateway_GET("/my-resource")
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
THE ISSUE
This code throws CORS Error. I can assure that I have enabled CORS on the API Gateway by selecting the Enable CORS option for each and every resource.
Following is the error
Access to XMLHttpRequest at 'https://this.is.a.dummy.base.url/v0/my-resource' from origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
But when I try the same using Postman, it works.
Can someone please help me get rid of the CORS Error ?
Thanks in advance.

NestJs Cookies when serving API into Heroku

const validateUser = await this.authService.validateUser(email, password);
const jwt = await this.authService.login(validateUser);
const cookie = response.cookie('jwt', jwt.access_token, { httpOnly: true });
Environment :
Nuxt.js host on Netlify
Nest.js host on Heroku
I'm using cookies in local development to make request after being loged.
But when I'm trying to host the front and back into Netlify and Heroku, the cookies do not be set (with the same configuration)
Is there a config needed to make it work ?
Edit :
response.cookie('jwt', jwt.access_token, {
httpOnly: true,
sameSite: 'none',
secure: true,
});
Google chrome need this configuration to make cookies working

react-scripts proxy a http/https server is loading the web in response

i am having a https create-react-app application(https://xyz.site.com), i am proxing a server which is a different domain, when i am loading the application the api is giving the web html data as a response, there is no hit happened in the server,
i have tried using HTTPS=true in .env file, still i am not able to get the server response
setupProxy.js
module.exports = (app) => {
app.use(
'/api',
createProxyMiddleware({
target: process.env.REACT_APP_API_URL, // https://xxx-alb-23333.us-west-2.elb.amazonaws.com
changeOrigin: true,
}),
);
};

Session cookie is not set in browser

I have frontend client running on custom Next.js server that is fetching data with apollo client.
My backend is graphql-yoga with prisma utilizing express-session.
I have problem with picking correct CORS settings for my client and backend so cookie would be properly set in the browser, especially on heroku.
Currently I am sending graphql request from client with apollo-client having option
credentials: "include" but also have tried "same-origin" with no better result.
I can see cookie client in response from my backend in Set-Cookie header, and in devTools/application/cookies. But this cookie is transparent to browser and is lost on refresh.
With this said I also tried to implement some afterware to apollo client as apolloLink that would intercept cookie from response.headers but it is empty.
So far now I'm thinking about pursuing those two paths of resolving the issue.
(I'm only implementing CORS because browser prevents fetching data without it.)
CLIENT
ApolloClient config for client-side:
const httpLink = new HttpLink({
credentials: "include",
uri: BACKEND_ENDPOINT,
});
Client CORS usage and config:
app
.prepare()
.then(() => {
const server = express()
.use(cors(corsOptions))
.options("*", cors(corsOptions))
.set("trust proxy", 1);
...here goes rest of implementation
const corsOptions = {
origin: [process.env.BACKEND_ENDPOINT, process.env.HEROKU_CORS_URL],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie", '*'],
methods: "GET,POST",
optionsSuccessStatus: 200
};
My atempt to get headers from response in apolloClient(but headers are empty and data is not fetched afterwards):
const middlewareLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const context = operation.getContext();
const {response: {headers}} = context;
if (headers) {
const cookie = response.headers.get("set-cookie");
if (cookie) {
//set cookie here
}
}
return response;
});
});
BACKEND
CORS implementaion (remeber that is gql-yoga so I need first to expose express from it)
server.express
.use(cors(corsOptions))
.options("*", cors())
.set("trust proxy", 1);
...here goes rest of implementation
const corsOptions = {
origin: [process.env.CLIENT_URL_DEV, process.env.CLIENT_URL_PROD, process.env.HEROKU_CORS_URL],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
exposedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Forwarded-Proto", "Cookie", "Set-Cookie"],
methods: "GET,HEAD,PUT,PATCH,POST,OPTIONS",
optionsSuccessStatus: 200
};
Session settings, store is connect-redis
server.express
.use(
session({
store: store,
genid: () => uuidv4(v4options),
name: process.env.SESSION_NAME,
secret: process.env.SESSION_SECRET,
resave: true,
rolling: true,
saveUninitialized: false,
sameSite: false,
proxy: STAGE,
unset: "destroy",
cookie: {
httpOnly: true,
path: "/",
secure: STAGE,
maxAge: STAGE ? TTL_PROD : TTL_DEV
}
})
)
I am expecting session cookie to be set on the client after authentication.
Actual result:
Cookie is visible only in Set-Cookie response header but is transparent to browser and not persistent nor set (lost on refresh or page change). Funny enough I can still make authenticated requests for data.
This may not be a CORS issue, it looks like a third-party cookie problem.
Behaviour could be different across browsers so I recommend testing several ones. Firefox (as of version 77) seems to be less restrictive. In Chrome (as of version 83) there is an indicator on the far right of the URL bar when a third party cookie has been blocked. You can confirm whether third party cookies is the cause of the problem by creating an exception for the current website.
Assuming your current setup is as follows:
frontend.com
backend.herokuapp.com
Using a custom domain for your backend that is a subdomain of your frontend would solve your problem:
frontend.com
api.frontend.com
The following setup wouldn't work because herokuapp.com is included in the Mozilla Foundation’s Public Suffix List:
frontend.herokuapp.com
backend.herokuapp.com
More details on Heroku.

mutli-domain cookies in express redirecting localhost to mydomain.com

I've got an app where I'm trying to allow the user to create their own subdomains.
In order to get this working locally while developing and testing, I've had to setup a few available subdomains in my hostfile.
For login, I'm using oauth, and google is the first provider I'm working with.
When a user logs in from mydomain.com google forces the redirect auth to localhost. I've written a bit of middleware that checks if the req.get('host') is localhost, it redirects to mydomain.com.
This seems to work fine, except that it appears that express doesn't match the sessions from the one returned by localhost when the redirect goes to mydomain.com.
As this is only for dev and testing, is there a way to allow express to share all the session info across domains, or add a whitelist?
The stuff I've found on SO has pointed me to have my redirect middleware as
module.exports = function(baseUrl){
var base = baseUrl.split(':');
base = base[1].replace('//','');
return function(req, res, next) {
console.log('in redirect');
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', base);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
if(req.hostname.indexOf('localhost') > -1) {
var redirect = req.get('host').replace('localhost', base);
console.log('redirect is', redirect, req.path);
return res.redirect(redirect + '/');
}
next();
}
}
but then in my post login route, I need to check if the requested url is localhost, and if so, then I need to replace it. For some reason, the middleware isn't being called in this route. It is called when I initially
function createUrl(req, subdomain){
var domain = req.get('host') === 'localhost:3000' ? 'mydomain.com:3000' : req.get('host');
var url = req.protocol + '://' + subdomain + '.' + domain;
return url + '/team'
}
function redirectToSubdomain(req, res){
var url = createUrl(req, "me");
console.log('new url is ', url);
res.redirect(url)
}
router.get('/', auth.isAuthenticated(), function(req, res) {
var team = Team.getSubdomain(req);
if(team.error) return redirectToSubdomain(req, res);
return res.render('team', req.data);
});
I've tried setting my cookie to accept all domains as
app.use(passport.session({cookie: { secure: true, domain: '*'}}));
I ended up not using dev.localhost as the main url to the development site, than subdomain.dev.localhost for subdomains, and in my cookie set domain: '.dev.localhost'. Worked a treat.

Resources