Use server-side cookies for effective tracking, session management, and personalized user experiences.
10 minute read
As the user privacy measures evolve, modern web browsers, particularly Safari, have introduced Intelligent Tracking Prevention (ITP) to limit user tracking through cookies. This poses serious challenges for businesses relying on cookies for authentication, personalization, and analytics.
To address these limitations, the JavaScript SDK offers a server-side cookie management solution to ensure data tracking without compromising the user privacy. This approach allows you to set RudderStack’s cookies on the server, providing full control over attributes like expiration and extending cookie lifespans unaffected by ITP’s restrictions.
What is ITP?
Intelligent Tracking Prevention (ITP) is a privacy feature introduced in Safari in 2017 to prevent advertisers from tracking users across websites. It restricts third-party cookies and may significantly shorten the lifespan of first-party cookies if they appear to be used for cross-site tracking.
ITP also affects how developers can use cookies for:
User authentication
Content personalization
Analytics and cross-site tracking
Session management and user experience continuity
The JavaScript SDK traditionally uses client-side cookies. With the introduction of ITP, it also implements server-side cookies to achieve:
Extended lifespan than the client-managed cookies.
Consistent and uniform experience across all the browsers.
rudderanalytics.load(WRITE_KEY,DATA_PLANE_URL,{useServerSideCookies:true// Default is false
});
Once enabled, the SDK makes network requests to the website’s server to set cookies via the response headers.
To ensure that the cookies are set successfully, you must make the request only to the website’s domain (or parent domain) server. RudderStack supports the following use cases:
Website
Server
Cookies
Parent domain website
Parent domain server
Cookies are created for the parent domain.
Sub-domain website
Sub-domain server
Cookies are created for the sub-domain.
Sub-domain website
Parent domain server
Cookies are created for the parent domain.
For sub-domain websites to set cookies for the parent domain, secure and sameSite cookie attributes are set to true and None respectively.
Otherwise, the browser will not set the server-side cookies.
A sample request flow is illustrated below:
rsaRequest is the default endpoint used by the JavaScript SDK to make the POST requests for setting the cookies.
Configure cookies
All the configuration parameters for client-side cookies are applicable for server-side cookies as well.
Additionally, there are two parameters which determine the cookies’ domain and the request URL:
Let’s assume there are two websites where the server-side cookies feature is enabled in the JavaScript SDK, namely https://example.com (parent website) and https://sub.example.com (sub-domain website).
The following are the possible combinations of the configuration options and cookie domains:
The JavaScript SDK can successfully manage cookies if your website’s server is proficient to handle these requests and responds with the appropriate cookie headers. You can ensure this by using any of the following methods:
Proxy
RudderStack’s data plane can handle these cookie requests out of the box.
This method involves setting up a proxy between the RudderStack data plane and the website to handle the cookie requests from the SDK.
Depending on the existing setup of your website, you can implement proxy as follows:
CDN
If your website is served via CDN, you can update it to support the cookie requests endpoint.
The following steps outline how to configure AWS CloudFront to proxy the requests to data plane. The configuration process is more or less similar for the other tools.
Create a custom origin
Setting
Description
Origin Domain
Enter the data plane URL from your RudderStack dashboard.
Name
Provide a unique name for this origin.
Create behavior for /rsaRequest endpoint
Setting
Description
Path Pattern
Enter /rsaRequest.
Origin and origin groups
Select the Name created in the previous step.
Viewer Protocol Policy
Set to HTTPS Only or Redirect HTTP to HTTPS, as required.
Allowed HTTP Methods
Select GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE.
Edit the origin request policy
Setting
Description
Name
Enter the name of custom origin request policy, for example Custom-Origin-Request-Policy.
Headers
Select headers as shown below, allowing even the sub-domain sites to make cookie requests. Otherwise, you will face CORS errors for the OPTION requests.
Enter the following settings
Setting
Description
Cache key and origin requests
Select Cache policy and origin policy (recommended)
Cache Policy
Select CachingDisabled as these are POST requests.
Origin Request Policy
Select the name of custom origin request policy created in the previous step.
By following the above steps, you can configure CloudFront to proxy specific requests to a different origin using behaviours and custom cache policies.
You can also enable logging in CloudFront to monitor requests and troubleshoot any issues. See AWS CloudFront documentation for more information.
Reverse proxy
This method outlines the changes to the reverse proxy configuration to handle the cookie requests from the SDK.
The following sample snippet is for Nginx but the implementation is similar for other proxies like Apache or HAProxy.
Ensure that the request and response headers are not stripped off in the Nginx proxy configuration.
Custom implementation
If you have another custom implementation of your website, contact the RudderStack team to share a sample implementation for your endpoint to handle the cookie requests from the JavaScript SDK.
Sample snippets
typeCookieData={name: string;value: string;};typeCookiesReqData={options:{expires?: Date;maxAge?: number;path?: string;domain?: string;sameSite?:'Lax'|'Strict'|'None';secure?: boolean;httpOnly?: boolean;};cookies: CookieData[];};typeRequestData=CookiesReqData;constALLOWED_COOKIES=['rl_user_id','rl_trait','rl_anonymous_id','rl_group_id','rl_group_trait','rl_page_init_referrer','rl_page_init_referring_domain','rl_session','rl_auth_token'];constencode=(value: string)=>{try{returnencodeURIComponent(value);}catch(err){returnundefined;}};constgenerateCookieStrings=(data: RequestData):string[]=>{try{const{cookies,options}=data;returncookies.filter((cookie: CookieData)=>ALLOWED_COOKIES.includes(cookie.name)).map((cookie: CookieData)=>{letcookieStr=`${encode(cookie.name)}=${encode(cookie.value)}`;letopts={...options};// Clone options to avoid modifying the original object
if(cookie.value===''){opts.maxAge=-10*60*1000;// Set negative to ensure cookie is deleted
}if(opts.maxAge){opts.expires=newDate(+newDate()+opts.maxAge);}if(opts.path)cookieStr+=`; Path=${opts.path}`;if(opts.domain)cookieStr+=`; Domain=${opts.domain}`;if(opts.expires)cookieStr+=`; Expires=${opts.expires.toUTCString()}`;if(opts.sameSite)cookieStr+=`; SameSite=${opts.sameSite}`;if(opts.secure)cookieStr+='; Secure';returncookieStr;});}catch(err){return[];}};exportdefaultfunctioncookieRequestHandler(ctx: Context){constrequestData: RequestData=ctx.request.body;constcookieStrings=generateCookieStrings(requestData);cookieStrings.forEach(cookieStr=>{ctx.response.append('Set-Cookie',cookieStr);});ctx.status=200;}
typeCookieData={name: string;value: string;};typeCookiesReqData={options:{expires?: Date;maxAge?: number;path?: string;domain?: string;sameSite?:'Lax'|'Strict'|'None';secure?: boolean;httpOnly?: boolean;};cookies: CookieData[];};typeRequestData=CookiesReqData;constALLOWED_COOKIES=['rl_user_id','rl_trait','rl_anonymous_id','rl_group_id','rl_group_trait','rl_page_init_referrer','rl_page_init_referring_domain','rl_session','rl_auth_token'];constencode=(value: string)=>{try{returnencodeURIComponent(value);}catch(err){returnundefined;}};constgenerateCookieStrings=(data: RequestData):string[]=>{try{const{cookies,options}=data;returncookies.filter((cookie: CookieData)=>ALLOWED_COOKIES.includes(cookie.name)).map((cookie: CookieData)=>{letcookieStr=`${encode(cookie.name)}=${encode(cookie.value)}`;letopts={...options};// Clone options to avoid modifying the original object
if(cookie.value===''){opts.maxAge=-10*60*1000;// Set negative to ensure cookie is deleted
}if(opts.maxAge){opts.expires=newDate(+newDate()+opts.maxAge);}if(opts.path)cookieStr+=`; Path=${opts.path}`;if(opts.domain)cookieStr+=`; Domain=${opts.domain}`;if(opts.expires)cookieStr+=`; Expires=${opts.expires.toUTCString()}`;if(opts.sameSite)cookieStr+=`; SameSite=${opts.sameSite}`;if(opts.secure)cookieStr+='; Secure';returncookieStr;});}catch(err){return[];}};exportdefaultfunctioncookieRequestHandler(req: NextApiRequest,res: NextApiResponse){constrequestData: RequestData=req.body;constcookieStrings=generateCookieStrings(requestData);cookieStrings.forEach(cookieStr=>{res.append('Set-Cookie',cookieStr);});res.status(200);}
$allowedCookies=['rl_user_id','rl_trait','rl_anonymous_id','rl_group_id','rl_group_trait','rl_page_init_referrer','rl_page_init_referring_domain','rl_session','rl_auth_token'];functionisAllowedCookie($name){returnin_array($name,$GLOBALS['allowedCookies']);}functionsetCookiesFromRequest($data){$cookies=$data['cookies'];$options=$data['options'];foreach($cookiesas$cookie){if(!isAllowedCookie($cookie['name'])){continue;}$cookieOptions=[];// Array to hold cookie-specific options
$maxAge=$cookie['maxAge'];if($cookie['value']===''){$maxAge=-10*60;// Set negative to ensure cookie is deleted
}$expires=$cookie['expires'];if(isset($maxAge)){$expires=time()+$maxAge;}// Set each option only if it's explicitly provided
if(isset($expires)){$cookieOptions['expires']=$expires;}if(isset($options['path'])){$cookieOptions['path']=$options['path'];}if(isset($options['domain'])){$cookieOptions['domain']=$options['domain'];}if(isset($options['secure'])){$cookieOptions['secure']=$options['secure'];}if(isset($options['sameSite'])){$cookieOptions['samesite']=$options['sameSite'];}setcookie($cookie['name'],$cookie['value'],$cookieOptions);}}functioncookieRequestHandler(){$jsonData=json_decode(file_get_contents('php://input'),true);setCookiesFromRequest($jsonData);}
ALLOWED_COOKIES=['rl_user_id','rl_trait','rl_anonymous_id','rl_group_id','rl_group_trait','rl_page_init_referrer','rl_page_init_referring_domain','rl_session','rl_auth_token'].freezedefcookie_request_handlerdata=JSON.parse(request.body.read)cookies_data=data['cookies']options=data['options'].symbolize_keyscookies_data.eachdo|cookie|nextunlessALLOWED_COOKIES.include?(cookie['name'])# Set max_age to a negative value if the cookie should be deletedmax_age=cookie['value'].blank??-10*60:(options[:maxAge]||0)# Calculate expiration time directly based on max_ageexpires=Time.current+max_age.secondscookie_options={value:value,max_age:max_age,expires:expires,path:options[:path],domain:options[:domain],secure:options[:secure],httponly:options.fetch(:httpOnly,false),same_site:options[:sameSite]}cookies[cookie['name']]=cookie_optionsendhead:okend
This site uses cookies to improve your experience while you navigate through the website. Out of
these
cookies, the cookies that are categorized as necessary are stored on your browser as they are as
essential
for the working of basic functionalities of the website. We also use third-party cookies that
help
us
analyze and understand how you use this website. These cookies will be stored in your browser
only
with
your
consent. You also have the option to opt-out of these cookies. But opting out of some of these
cookies
may
have an effect on your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. This
category only includes cookies that ensures basic functionalities and security
features of the website. These cookies do not store any personal information.
This site uses cookies to improve your experience. If you want to
learn more about cookies and why we use them, visit our cookie
policy. We'll assume you're ok with this, but you can opt-out if you wish Cookie Settings.