Express JS how to redirect all http traffic to https (Heroku) - reactjs

I want a mechanism in my Express JS app to redirect all http traffic to https traffic, similar to Rails's force_ssl config. (In Rails's case, it simply sends back a Redirect response to the client
I think the Express server code would go in Server.js, but I need to distinguish these requests from secure (https) requests that should go to the real app.
I think the redirection code looks like this:
var http = express.createServer();
http.get('*', function(req, res) {
res.redirect('https://' + req.headers.host + req.url);
})
// have it listen on 8080
http.listen(80);
You'll note that I can't actually listen for the port by port number, because on Heroku there's the added complication that the app may be redeployed listening on app port of their choosing (it changes every time is redeployed).
so, in essence, I need a way to detect the protocol (http or https) without using the port number here in the Express setup, and then redirect http traffic to https.
The app itself is modeled after "Create React App on Heroku" here https://originmaster.com/running-create-react-app-and-express-crae-on-heroku-c39a39fe7851, so essentially it is a REACT app being served by Express JS on Heroku.
and with example app here https://github.com/Johnnycon/crae-heroku
where I'm stuck is that i've examined the 'process' variable, and while it contains a lot of information, it does not seem to contain any request information, like the protocol or url, as the request comes in.
Any tips or suggestions?

Typically in your server.js near the top, add this function:
// On Heroku, SSL termination happens at the load balancer,
// BEFORE encrypted traffic reaches your node app.
function enforceHttps(req, res, next) {
// Check if directly requested via https
if (req.secure) {
next();
// Heroku sets a header X-Forwarded-Proto to pass the user requested protocol
} else if ((req.headers['x-forwarded-proto'] || '').substring(0, 5) === 'https') {
next();
// Only redirect GET and HEAD requests
} else if (req.method === 'GET' || req.method === 'HEAD') {
const host = req.headers['x-forwarded-host'] || req.headers.host;
// redirect with 301 Moved Permanently instead of default 302
res.redirect(301, `https://${host}${req.originalUrl}`);
} else {
res.status(403).send('This server requires an HTTPS connection.');
}
}
and then later in the file after you define you app, typically as const app = express();:
app.use(enforceHttps);

Related

Why won't my React app send HTTP-only cookies in WebSocket upgrade requests in production?

I'm currently building a full-stack TypeScript live chat app with React + Vite on the frontend and Node on the backend. I have two separate servers running: one is a REST API and OAuth2 auth server built with Express and Passport.js and the other one is a WebSockets server built with the ws package. They run independently (no interprocess communication whatsoever) and use stateless auth in the form of JWTs.
Here's how my current flow works: users first log in with either their Google or GitHub account, and once the first server has verified their identity, it sends an HTTP-only cookie down to the client. This cookie is send back to the server on all subsequent requests and I have some middleware that runs on the REST API to parse and verify the JWTs on protected routes. Once it has the cookie, the client then initiates a WS connection with the second server, which also checks for the JWT cookie in the incoming HTTP Upgrade request and verifies its signature before allowing the new client to continue exchanging messages:
import { WebSocket, WebSocketServer } from "ws";
import { baseDataSchema } from "./zod/schemas";
import prisma from "./prisma";
import { asyncJWTverify } from "./misc/jwt";
import { UserJwtReceived } from "../types/jwt";
import { handleJoinGroup } from "./websockets-handlers/join-group";
// Websockets server setup
const wss = new WebSocketServer({ port: Number(process.env.WS_PORT) });
const userSocketMap = new Map<string, WebSocket>();
wss.on("listening", () => {
console.log(`WebSockets server started on port ${process.env.WS_PORT}`);
});
wss.on("connection", async function connection(ws, req) {
// authenticate incoming websocket connection
const cookies = req.headers.cookie;
if (!cookies) return ws.close();
let currentUser: UserJwtReceived = { id: "", iat: 0 };
try {
// Decode auth JWT
const token = cookies.split("=")[1];
currentUser = (await asyncJWTverify(
token,
process.env.JWT_SECRET as string
)) as UserJwtReceived;
} catch (err) {
console.error(err);
return ws.close();
}
// check for JWT expiry
const expiryTime = Number(process.env.JWT_EXPIRY);
if (Math.round(Date.now() / 1000) - currentUser.iat > expiryTime) {
return ws.close();
}
// Bind user ID to WebSocket, add it to map
// TypeScript doesn't complain about this because I've extended ws's WebSocket interface
ws.userId = currentUser.id;
userSocketMap.set(currentUser.id, ws);
console.log(`User ID ${ws.userId} connected`);
ws.on("message", async function message(rawData) => {
// ... actual app logic goes here
})
ws.on("close", function () {
if (!ws.userId) return;
console.log(`User ID ${ws.userId} has disconnected`);
userSocketMap.delete(ws.userId);
});
})
Both servers and the React frontend app run on different URLs, both on local dev and prod, so all requests are cross-origin, but CORS is enabled on the REST API/auth server and as far as I know the WebSockets protocol doesn't implement any CORS policies...
The problem I'm currently facing is that in my local dev environment, the cookie that contains the JWT is sent along with Upgrade request no problem, but after deploying my app to AWS Lightsail (it's a VPS service similar to EC2) and setting up NGINX, my React frontend is no longer able to include the cookie with the upgrade request.
After spending literally the whole day debugging, I've been able to rule out a faulty NGINX config as the root of the problem, since I can use wscat to connect (and most importantly, successfully authenticate) to my production WS server by manually including the Cookie header.
I still have no idea why my React app won't properly send the HTTP-only auth cookie to my WS server. Does anyone have any clue as to why this is happening?
I expected the HTTP-only cookie containing the JWT to be sent along with the HTTP Upgrade request, just like I've been able to do in my local dev environment, but no luck.

HTTP to HTTPS: Express & Heroku

Heroku suggests to use NPM packages to handle forcing SSL for express apps. I've tried about a dozen without success. The only thing I can get to work is shown below but then Google gets mad.
Question: Can someone please tell me a working method as of 2022 to force HTTPS on an express app?
<script>
//This works but Google Console gets angry: Page indexing issues: Page with redirect
var host = "www.URL.com" || "URL.com";
if ((host == window.location.host) && (window.location.protocol != "https:")){
window.location.protocol = "https";
}
</script>
If you set the Strict-Transport-Security header, the browser will remember that this is an HTTPS only site after accessing it over HTTPS for the first time. The browser then substitutes every subsequent request to http://your.server with a request to https://your.server, so it will never again visit your site over HTTP.
To ensure that a first visit over HTTPS happens, add a permanent redirect from HTTP to HTTPS. (I don't think this redirect should upset Google, for example, github.com does it in the same way.)
The following code assumes that Heroku runs the app on both HTTP and HTTPS for you.
app.use(function(req, res, next) {
if (!(req.client instanceof tls.TLSSocket))
return res.redirect(301, "https://your.server");
res.set("Strict-Transport-Security", "max-age=86400");
next();
});

Cors issue i also set the proxy in pkg.json due to cors and set the api according to it but this error is not remove

GET http://localhost:3000/api/fetch?search=12312321 404 (Not Found)
cors issue in Reactjs and node js
i use the proxy method in Reactjs but can't get rid from it please help me
proxy
"proxy":"http://localhost:5000/"
tried both
"proxy":"http://localhost:5000"
express
RegisterRoute.route('/fetch/:id').get( ( req , res) =>
{
console.log("called by someone ",req.params.id);
res.send("okey will");
});
Reactjs function which will call the backend api
FetchNotification(){
axios({
'method':'GET',
'url':'api/fetch',
'headers': {
'content-type':'application/octet-stream',
'x-rapidapi-host':'example.com',
'x-rapidapi-key': process.env.RAPIDAPI_KEY
}
,
'params': {
'id':'12312321'
},
})
}
when i simply call axios.get it perfectly work but when i give params to it it gives the error xhr:178 404 not found
a simple server which also returns the same result
const express = require('express');
const cors= require('cors');
const app= express();
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());
app.use('*',cors());
app.get('/fetch/:id',(req , res) => {
console.log("calling",req.params.id);
});
app.listen(5000);
I can see that you're using Nodejs as server side. So, you can try changing following line
app.use('*',cors());
to
app.use(cors());
If this doesn't solve the issue, you can try adding a google chrome extension for CORS (Most probably). In this way, you'll not need any proxies being set for running servers.
Also, you need to do a small change in URL, instead of calling
'url':'api/fetch'
you need to provide an id in your call, because the backend api is accepting a parameter
'url':'api/fetch/some_id'
I feel there are multiple issues. I'll try to address them one by one.
Firstly, if you are proxying your requests correctly (which I think you are as per your package.json), then you'd not require the cors package. So you can get rid of that package.
Read more about why you shouldn't let all incoming request bypass the CORS check from security point of view -> Is it safe to enable CORS to * for a public and readonly webservice?
Now secondly, the url which you've specified on the frontend is 'url':'api/fetch', which means browser will make a call to http://localhost:3000/api/fetch?search=12312321 which it correctly did as seen in your error statement for 404.
Specifying the proxy as "proxy":"http://localhost:5000" in package.json means that now you are making requests to http://localhost:5000 instead of http://localhost:3000, but the browser would still think its http://localhost:3000. That's the whole purpose of proxying and how you kinda fool browser to do CORS without throwing any error.
But because on your server, you are listening to app.get('/fetch/:id',(req , res) instead of app.get('api/fetch/:id',(req , res), it doesn't matches with this URL as you have not explicitly handled requests starting with /api in some separate router module either.
So you should either update the url in the axios call to url':'/fetch while the proxy value in package.json is "proxy":"http://localhost:5000" or url':'fetch and "proxy":"http://localhost:5000/" respectively. Notice how i've used the /
OR
You can update the URL on the express end to app.get('api/fetch/:id',(req , res)
Lastly, whenever you receive a request, you need to return some value(string or JSON or even status code) as result.
In your case, you've simply console.log("calling",req.params.id); and didn't send back any response to the UI.
So even if your 404 would resolve after fixing the URL, you'd bump into request timeout error (408) as your UI will keep waiting for a response from the server while you didn't send any.
So you should do something like this maybe:
app.get('api/fetch/:id',(req , res) => {
console.log("calling",req.params.id);
res.send("It worked");
});
Notice how there's a response being sent back -> https://expressjs.com/en/starter/hello-world.html
If you don't want to send back a response, then you can simply return status like 200, 504 etc.
if you apply below code in backend/server i thing it will debug.
app.get('api/fetch/:id',(req , res) => {
console.log("calling",req.params.id);
res.send("It worked");
});

How is it possible to run Angularjs webapi at the same time as http and https?

I want to provide http and https support for web based angularJS project. If requested, it will work as https as well as http.
I ran the web API with https. All request and response operations are secure.
How can project run (Web and Web API projects) both with http and https?
Where and how do I have to manage / redirect requests that may come as http or https?
There is something that I think will help you with the question. In the configure options of Web API, only one value is allowed as project / url. (Look picture)
On the web side of the project, url and port variants can be highly improved and defined independently. So I will not have a problem there.
RequireHttpsAttribute ( if have to use )
public override void OnAuthorization(HttpActionContext actionContext)
{
//if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
//{
// actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Found);
// actionContext.Response.Content = new StringContent("<span> Http yerine artık https kullanılıyor.</span>");
// UriBuilder uriBuilder = new UriBuilder(actionContext.Request.RequestUri);
// uriBuilder.Scheme = Uri.UriSchemeHttps;
// uriBuilder.Port = 44332;
// actionContext.Response.Headers.Location = uriBuilder.Uri;
//}
//else
//{
// base.OnAuthorization(actionContext);
//}
}
WebAPI Properties
Project Url
Web Properties
Project Url
config.js (web-side)
//serviceBase: "http://localhost:61456/",
//servicePort: "61456",
serviceBase: "https://localhost:44332/",
servicePort: "44332"
You should change project property (Ctrl+W, P) 'SSL Enabled' to true. After this you will be able to request as http as well https at the same time.
Project Properties

Unable to call to API after deploying app to Heroku

I've made a weather app that makes an API call to freegeoip to locate your current location's coordinates, and then using those coordinates to connect to openweathermap API to fetch your current location's weather.
In development the app worked perfectly fine. But after deploying to Heroku, I seem to get what looks like a CORS error?
Console logs:
Mixed Content: The page at 'https://weather-react-drhectapus.herokuapp.com/' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://freegeoip.net/json/'. This request has been blocked; the content must be served over HTTPS.
Link to Heroku app
EDIT:
Changing to https seems to work for the freegeoip API (https://freegeoip.net/json/), but doesn't work for the openweathermap API. This is the full console log I get:
GET https://api.openweathermap.org/data/2.5/weather?appid=95108d63b7f0cf597d80c6d17c8010e0&lat=49.25&lon=4.0333 net::ERR_CONNECTION_CLOSED
bundle.js:16 Uncaught (in promise) Error: Network Error
at e.exports (bundle.js:16)
at XMLHttpRequest.d.onerror (bundle.js:16)
Google Maps API warning: NoApiKeys https://developers.google.com/maps/documentation/javascript/error-messages#no-api-keys
Google Maps API error: MissingKeyMapError https://developers.google.com/maps/documentation/javascript/error-messages#missing-key-map-error
Just change API endpoint to use https instead of http.
https://freegeoip.net/json/ works well ;)
Update
Your updated question contains one more request. Unfortunately, api.openweathermap.org is not available over HTTPS. Thus, you need to reach it thru proxy under your control and forward response to your client. For more info, see this answer
If you apply this middleware it should start working correctly
app.use(function (req, res, next) {
if (req.headers['x-forwarded-proto'] === 'https') {
res.redirect('http://' + req.hostname + req.url);
} else {
next();
}
});

Resources