Background
I set up an Express.js app behind a proxy to let users to login before being directed to a web app. This app is failing to serve up some images in Safari (macOS/iOS) because Safari is not sending the cookie back with requests for images that originate from the loadImage()
method in my p5.js code. This does not happen on Chrome (macOS).
When I load the page, the browser requests the resources fine. But requests originating from my application returns a different session, which is not logged in, and gets caught by Express:
// Request for the main page by the browser
Session {
cookie:
{ path: '/',
_expires: 2020-05-04T16:26:00.291Z,
originalMaxAge: 259199998,
httpOnly: true,
secure: true },
loggedin: true }
// Request for image assets by a script in my application
Session {
cookie:
{ path: '/',
_expires: 2020-05-04T16:26:00.618Z,
originalMaxAge: 259200000,
httpOnly: true,
secure: true } }
HTTP Requests from Safari
GET https://mydomain/app/img/svg/Water.svg HTTP/1.1
Host: mydomain
Origin: https://mydomain
Connection: keep-alive
If-None-Match: W/"5e6-171c689d240"
Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5
If-Modified-Since: Wed, 29 Apr 2020 15:24:13 GMT
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
Referer: https://mydomain/app/
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br
HTTP/1.1 302 Found
Date: Fri, 01 May 2020 06:50:07 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.17
X-Powered-By: Express
Location: /
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 23
Access-Control-Allow-Origin: *
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Found. Redirecting to /
Express app
The app is set up behind an HTTPS proxy, so I set the Express Session object to trust proxy and set security to auto (setting to false doesn’t fix the problem):
app.set('trust proxy', 1)
app.use(session({
secret: 'my-secret',
resave: true,
saveUninitialized: true,
cookie: {
secure: 'auto',
maxAge: 259200000
}
}));
When the user signs in, it is sent to /auth
to check against the database
app.post('/auth', function (request, response) {
var user = request.body.user;
var password = request.body.password;
if (user && password) {
connection.query('SELECT * FROM accounts WHERE user = ? AND password = ?', [user, password], function (error, results, fields) {
if (results.length > 0) {
request.session.loggedin = true;
// If the user logs in successfully, then register the subdirectory
app.use("/app", express.static(__dirname + '/private/'));
// Then redirect
response.redirect('/app');
} else {
response.send('Incorrect password!');
}
response.end();
if (error) {
console.log(error);
}
});
} else {
response.send('Please enter Username and Password!');
response.end();
}
});
They are redirected to /app
when logged in:
app.all('/app/*', function (request, response, next) {
if (request.session.loggedin) {
next(); // allow the next route to run
} else {
// The request for images from my p5 script fails and these request redirect to "/"
response.redirect("/");
}
})
Question
What can I do to ensure Safari pass the session cookie with its request so that Express will return the correct asset?
Edit
Including the function that invokes loadImage()
. This is embedded in an ES6 class that loads image assets for particles in a chemical simulation. This class must successfully resolve promises so other higher order classes can set correct properties.
loadParticleImage() {
return new Promise((resolve, reject) => {
loadImage(this.imageUrl, (result) => {
// Resolves the Promise with the result
resolve(result);
}, (reason) => {
console.log(reason);
});
})
}
Edit #2
Including the headers for a successful request directly to the URL of the image asset:
GET https://mydomain/app/img/svg/Water.svg HTTP/1.1
Host: mydomain
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Cookie: connect.sid=s%3A2frOCv41gcVRf1J4t5LlTcWkdZTdc8NT.8pD5eEHo6JBCHcpgqOgszKraD7AakvPsMK7w2bIHlr4
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
2
Answers
I suggest using express’s static middleware to serve static files. With this, you won’t need any session to get images, js, css, etc. Also, it accelerates your application. You need to place
before the
app.use(session( ... ))
statement if you want some additional perfomance, because if you do, express won’t attepmt to creare session for static files.The
fetch()
call in the source code for thatloadImage()
function is not setting thecredentials
option that controls whether cookies are included with the request or not, therefore they are not sent with the request.Do you really need authentication before serving an image? If not, you could rearrange the way you serve images in your server so that they can be served without authentication using
express.static()
pointed at a directory that contains only resources that can be served without authentication. If they do need to be authenticated, you may have to patch theloadImage()
code to use thecredentials: include
option or load your images a different way.