Server Side cookies
Safari has recently introduced Intelligent Tracking Prevention (ITP) 2.1, 2.2, and 2.3 which reduces trackers’ ability to establish user identities across sites.
- In ITP version 2.1, a cookie created on a visitor’s browser expires after 7 days.
- With ITP version 2.2, if a user comes from a decorated URL, i.e., a domain classified with cross-domain tracking capabilities with has a query string or a fragment, the cookies on the website expire within 24 hours.
- With ITP version 2.3, if a user comes from a domain classified with cross-domain tracking capabilities and has a query string or a fragment, the first-party non-cookie website data expires after 7 days of the user not interacting with the website.
How ITP Impacts Triggerbee
Although Triggerbee doesn't support cross tracking capabilities nor uses 3rd party cookies, the severe and broad limitations of cookies in ITP will affect Triggerbee negatively, including - but not limited to:
Reduced identification rates
Users identified in Triggerbee during one visit, that comes back after ITP has removed the cookie, will be unidentified again. Unidentified users will not be served the personalized campaigns that identified visitors get.
Less accurate A/B test
The same visitor visiting the website after 7 days may be served a different variation of the same A/B test than what was previously shown. This can create inconsistent experiences and impact campaign accuracy. This is because the information about which variation was shown to the user is stored in cookies.
Incorrect Returning visitor metrics
In Triggerbee you have audiences for new and returning visitors. These metrics are also reported under the "Analytics" section. With ITP Safari users will more often count as "new visitors", even though they recently visited the website.
Solution
To avoid ITP-related effects and increase the identification rate of your users. Increase the expiry date of the Triggerbee “_mtruid” cookie via your server.
Note that this operation is specific to your setup, and how it's implemented depends on your infrastructure and frontend framework.
Limitation: Server side-cookie updates have to be done on the first page load.
That means it cannot be updated by a server controller after the page has been initially loaded. Because if it's not updated directly, then Safari might not honor your intended long-term expiration. Safari can apply its ITP logic, causing the cookie to expire after just one week.
Caching issues: Why Caching Can Cause Cookie Issues
If your server or CDN caches responses, it might unintentionally send one user's cookie value to another user. This can happen because caching systems store and reuse responses to improve performance. However, when cookies like _mtruid are involved, improper caching can lead to privacy and functionality issues.
How This Happens:
- Caching Responses: The server or CDN stores the entire response, including headers and cookies, after processing a request.
- Shared Cache: When another user accesses the same resource, the cached response is reused. If the cookie isn’t handled properly, the cached response may include the previous user’s cookie value.
- Result: This could expose one user's _mtruid value to another, potentially causing data mismatches and profiles would be mixed up.
How to Prevent This Issue
To avoid caching-sensitive cookie data, follow these best practices:
1. Set Cache-Control Headers
Ensure your server response includes appropriate cache-control headers to prevent shared caching:
Cache-Control: no-store, no-cache, must-revalidate, private
private: Ensures the response is specific to the individual user.
no-store: Prevents the caching of any part of the response.
no-cache: Forces the server to revalidate the response before serving it.
2. Handle Cookies in Middleware
For edge platforms like Cloudflare or Vercel, use middleware to dynamically process cookies:
- Extract the _mtruid value at the edge.
- Serve responses that are unique to the user without relying on shared cache.
Summary:
To ensure secure and accurate handling of cookies in cached environments:
- Use Cache-Control headers to control caching behavior.
- Avoid caching responses that depend on sensitive cookies unless absolutely necessary.
- If caching is needed, configure it to vary by cookies or other user-specific attributes.
By implementing these strategies, you can avoid sending one user’s cookie value to another and ensure a secure, consistent experience for your users.
Here is an example with and without cookie updates.
Workflow Without Server-Side Cookie Updates:
- User visits the page for the first time. The Triggerbee client script creates the _mtruid cookie with a 1-year expiration date.
- Safari's Intelligent Tracking Prevention (ITP) overrides the expiration date, reducing it to 1 week.
- On the next visit, the cookie expires after 1 week, limiting its usability.
Workflow With Server-Side Cookie Updates:
- User visits the page for the first time. The server checks for the _mtruid cookie:
- If the cookie isn’t sent by the client, no action is taken.
- Safari ITP reduces the expiration date to 1 week.
- On subsequent page reloads, the _mtruid cookie is sent to the server. The server:
- Reads the cookie value.
- Recreates the cookie on the server with the same value and updates the expiration date to extend it.
- On future visits, the user benefits from the extended expiration date.
Pseudocode for updatingCookie, needs to be done in the initial pageload.
GET the '_mtruid' cookie value from the request and store it in mtruidCookie. IF mtruidCookie exists: PRINT "Updating _mtruid cookie expiration..." RE-SET the '_mtruid' cookie with: - Value: mtruidCookie.value - Max age: 1 day (60 seconds * 60 minutes * 24 hours) - Path: Root directory ('/') - Domain: '.domain.com' (adjust for production domain) - SameSite: 'lax' - HTTP-only: False ADD response headers: - Cache-Control: no-store, no-cache, must-revalidate, private ELSE: PRINT "_mtruid not present; not updating expiration."
Here is an example of how it could be implemented on a Next.js server. In the middleware.js / ts file.
import { type NextRequest } from 'next/server'; import { updateSession } from '@/utils/supabase/middleware'; export async function middleware(request: NextRequest) { // Get the response from updateSession const response = await updateSession(request); // Retrieve the '_mtruid' cookie const mtruidCookie = request.cookies.get('_mtruid'); if (mtruidCookie) { console.log('Updating _mtruid cookie expiration...'); // Re-set the '_mtruid' cookie with extended expiration response.cookies.set('_mtruid', mtruidCookie.value, { maxAge: 60 * 60 * 24, // 1 day path: '/', domain: '.domain.com, // Adjust to your production domain sameSite: 'lax', httpOnly: false, }); } else { console.log('_mtruid not present; not updating expiration.'); } // Add cache-control headers to prevent shared caching response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, private'); return response; } export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', ], };