Skip to content

Instantly share code, notes, and snippets.

@gmencz
Last active March 14, 2025 10:19
Show Gist options
  • Save gmencz/7d6973b8ab95f5adae1739e88f956f70 to your computer and use it in GitHub Desktop.
Save gmencz/7d6973b8ab95f5adae1739e88f956f70 to your computer and use it in GitHub Desktop.
type CookieOptions = {
maxAge?: number;
expires?: Date;
httpOnly?: boolean;
path?: string;
domain?: string;
secure?: boolean;
sameSite?: boolean | 'lax' | 'strict' | 'none';
};
type ParsedCookie = {
cookieName: string;
cookieValue: string;
options?: CookieOptions;
};
function hasExpiresField(cookie: string): boolean {
return cookie
.split(';')
.map(part => {
if (part.split('=').length > 1) {
return part.split('=')[0].trim().toLowerCase() === 'expires';
}
return false;
})
.some(bool => bool);
}
export default function parseCookies(rawCookies: string): ParsedCookie[] {
// Make this check more effective
if (!rawCookies.includes('=')) {
throw new Error(
'Invalid raw cookies, look at the format of a set-cookie header string and provide something similar.'
);
}
/*
Cookie format: "name=value; Path=/; HttpOnly; Secure"
Multiple cookies format: "name=value; Path=/; HttpOnly; Secure, name2=value2"
*/
const arraifyedRawCookies = rawCookies.split(',');
const validRawCookies = arraifyedRawCookies
.map((rawCookie, index, ref) => {
if (hasExpiresField(rawCookie)) {
return `${rawCookie}${ref[index + 1]}`;
}
if (index > 0 && hasExpiresField(ref[index - 1])) return 'invalid';
return rawCookie;
})
.filter(rawCookie => rawCookie !== 'invalid');
const parsedCookies = validRawCookies.map(rawCookie => {
const [cookieNameAndValue, ...cookieProperties] = rawCookie.split(';');
const [cookieName, cookieValue] = cookieNameAndValue.split('=');
const sanitizedCookieProperties = cookieProperties.map(cookieProperty => {
const [propertyName, propertyValue] = cookieProperty.split('=');
const sanitizedPropertyName = propertyName
.replace('-', '')
.toLowerCase()
.trim();
if (sanitizedPropertyName === 'maxage') {
return `maxAge=${propertyValue}`;
} else if (sanitizedPropertyName === 'httponly') {
return 'httpOnly=true';
} else if (sanitizedPropertyName === 'samesite') {
return `sameSite=${propertyValue.toLowerCase()}`;
}
if (!propertyValue) {
return `${sanitizedPropertyName}=true`;
}
return `${sanitizedPropertyName}=${propertyValue}`;
});
const objectifyedCookieProperties = sanitizedCookieProperties.map(
sanitizedCookieProperty => {
const [propertyName, propertyValue] = sanitizedCookieProperty.split(
'='
);
return {
[propertyName]: propertyValue === 'true' ? true : propertyValue,
};
}
);
const options: CookieOptions = {};
objectifyedCookieProperties.forEach(objectifyedCookieProperty => {
Object.entries(objectifyedCookieProperty).forEach(([key, value]) => {
if (key === 'expires') {
options[key] = new Date(value as string);
return;
}
if (key === 'maxAge') {
options[key] = Number(value as string);
return;
}
options[key] = value;
});
});
return {
cookieName: cookieName.trim(),
cookieValue: cookieValue.trim(),
options,
};
});
return parsedCookies;
}
@JamesDelfini
Copy link

JamesDelfini commented Jul 4, 2020

Thank you for your for this cookie parser and the solution of the federation issue setting of cookies with the services at Apollo federation cookies issue.

@Nighthawk14
Copy link

Hey, thanks for the piece of code. The splitCookiesString method of this library seems to do the same thing btw:
https://github.com/nfriedly/set-cookie-parser#splitcookiesstringcombinedsetcookieheader

@AhmedBHameed
Copy link

Finally!! find a script to parse cookies correctly :) Thanks for sharing.

Coming from this thread apollographql/apollo-server#3099 (comment)

@dberardo-com
Copy link

can it be that this code prevents cookies from being deleted ?

i run into some issues with the expires field because it contains a comma between the name of the day and the date, like Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 1900 07:28:00 GMT

any hints ?

@dberardo-com
Copy link

i am sorry, dates are handled correctly, but i believe this line is preventy httpOnly and secure to go through:

if (!propertyName || propertyValue === undefined) return null;

... i believe i had an older version of the script ...

BTW ... this is totally annoying that apollo people do not provide an official fix for this ...

@dberardo-com
Copy link

btw i have figured out that if a cookie value contains a comma the whole thing breaks ... is this avoidable ?

like using a JSON string inside a cookie value ?

@dberardo-com
Copy link

and also: shouldn't be cookies semicolon separated ?? https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cookie

why is the code above splitting by comma instead ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment