I am using CloudFront as cache in front of my Symfony web application. To get a cache based on a user's role (admin, customer,...) I generate a user role based hash in a Lambda@Edge Viewer Request trigger. I pass that hash on as a request header to my origin as X-User-Context-Hash
.
My problem is now that I need to pass the PHPSESSID
cookie on to my origin to get the right response for caching, but I do not want to base the cache on the value of PHPSESSID
. I do only need my cached response to be based on the value of X-User-Context-Hash
but not on my session cookie.
The image below should explain my problem in detail
Is there any possibility to accomplish that?
Would appreciate any help.
Here's my Lambda@Edge Viewer Request trigger:
'use strict';
function parseCookies(headers) {
const parsedCookie = {};
if (headers.cookie) {
console.log(`${headers.cookie[0].value}`);
headers.cookie[0].value.split(';').forEach((cookie) => {
if (cookie) {
const parts = cookie.split('=');
parsedCookie[parts[0].trim()] = parts[1].trim();
}
});
}
return parsedCookie;
}
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const https = require('https');
// Read session cookie
const parsedCookies = parseCookies(headers);
let cookie = '';
if (parsedCookies) {
if(parsedCookies['PHPSESSID']) {
cookie = `PHPSESSID=${parsedCookies['PHPSESSID']}`;
}
}
console.log(`Cookie: ${cookie}`);
// Send request to origin host at /_fos_user_context_hash
// passing the original session cookie
const options = {
hostname: `${request.headers.host[0].value}`,
port: 443,
path: '/_fos_user_context_hash',
method: 'HEAD',
headers: {
'Cookie': cookie,
'Accept': 'application/vnd.fos.user-context-hash',
'Vary' : 'Cookie'
}
};
const req = https.request(options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
// Read the X-User-Context-Hash from the hash endpoint
const headerName = 'X-User-Context-Hash';
let hash = 'anonymous';
if (res.headers[headerName.toLowerCase()]) {
hash = res.headers[headerName.toLowerCase()];
}
// Append X-User-Context-Hash before passing request on to CF
request.headers[headerName.toLowerCase()] = [{ key: headerName, value: hash }];
callback(null, request);
}).on('error', (e) => {
console.error(e);
// Forward request anyway
callback(null, request);
});
req.end();
}
;
Here's how I finally solved my problem:
I configured the behavior not to forward any cookies to the origin, but only cache based on the headers Host
and X-User-Context-Hash
(see screenshot).
The following image explains my lambda@edge process:
PHPSESSID
and REMEMBERME
and pass those values via the X-Session-Cookies
header on.Host
and X-User-Context-Hash
headers, Cloud-Front returns the cached item and stops here.X-Session-Cookies
is available. So I take the value from the X-Session-Cookies
header and set the value of request.headers.cookie
to that value. This step ensures that the PHPSESSID
and REMEMBERME
cookie are both passed to the origin before the page gets cached.The Viewer Request trigger:
'use strict';
function parseCookies(headers) {
const parsedCookie = {};
if (headers.cookie) {
console.log(`${headers.cookie[0].value}`);
headers.cookie[0].value.split(';').forEach((cookie) => {
if (cookie) {
const parts = cookie.split('=');
parsedCookie[parts[0].trim()] = parts[1].trim();
}
});
}
return parsedCookie;
}
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const https = require('https');
let sessionId = '';
// Read session cookie
const parsedCookies = parseCookies(headers);
let cookie = '';
if (parsedCookies) {
if(parsedCookies['PHPSESSID']) {
cookie = `PHPSESSID=${parsedCookies['PHPSESSID']}`;
}
if(parsedCookies['REMEMBERME']) {
if (cookie.length > 0) {
cookie += ';';
}
cookie += `REMEMBERME=${parsedCookies['REMEMBERME']}`;
}
}
console.log(`Cookie: ${cookie}`);
// Send request to origin host at /_fos_user_context_hash
// passing the original session cookie
const options = {
hostname: `${request.headers.host[0].value}`,
port: 443,
path: '/_fos_user_context_hash',
method: 'HEAD',
headers: {
'Cookie': cookie,
'Accept': 'application/vnd.fos.user-context-hash',
'Vary' : 'Cookie'
}
};
const req = https.request(options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
// Read the X-User-Context-Hash from the hash endpoint
const headerName = 'X-User-Context-Hash';
let hash = 'anonymous';
if (res.headers[headerName.toLowerCase()]) {
hash = res.headers[headerName.toLowerCase()];
}
// Append X-User-Context-Hash before passing request on to CF
request.headers[headerName.toLowerCase()] = [{ key: headerName, value: hash }];
const sessionHeaderName = 'X-Session-Cookies';
request.headers[sessionHeaderName.toLowerCase()] = [{ key: sessionHeaderName, value: cookie }];
callback(null, request);
}).on('error', (e) => {
console.error(e);
// Forward request anyway
callback(null, request);
});
req.end();
}
;
The Origin Request trigger:
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const sessionHeaderName = 'X-Session-Cookies';
let cookie = '';
if (request.headers[sessionHeaderName.toLowerCase()]) {
console.log(request.headers[sessionHeaderName.toLowerCase()]);
cookie = request.headers[sessionHeaderName.toLowerCase()][0].value;
}
request.headers.cookie = [{ key : 'Cookie', value : cookie }];
callback(null, request);
};
AWS recently introduced cache and origin request policies, allowing more customization.
You can now set cache behavior based on "All-except" list of cookies/query string params by setting appropriate cache policy and set origin request policy to forward only necessary data:
The fundamental problem:
If you configure CloudFront to forward cookies to your origin, CloudFront caches based on cookie values. This is true even if your origin ignores the cookie values in the request...
http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Cookies.html
This is by design. Cookies you forward are always part of the cache key.
There is no clean/simple/obvious workaround.
You could add the session cookie to the query string in the viewer request trigger, and configure that parameter to forward but not be used for caching, and then your origin would need to find it there and interpret it as a cookie. Query string parameters, unlike cookies, can be configured for forwarding, but not caching.
You could potentially replace the cookie in the actual request with a dummy/placeholder value, one per user class, so that it would be forwarded to the origin and used for caching, and then use a viewer-response trigger to prevent any Set-Cookie
response from the origin (or cache) from exposing that magic cookie to any viewers.
Really, though, it sounds as if you may be trying to solve a problem in one place that really needs to be solved in another. Your application has a limitation in its design that is not cache friendly for certain resources. Those resources need to be designed to interact in a cache-friendly way, which is of course a fundamentally tricky proposition when access to the resource requires authenticated identification of a user, role, group, permission, etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With