Cookies are one of the simplest things on a website to overlook from a security standpoint, but three flags — HttpOnly, Secure, and SameSite — cover most of the real-world attack paths that target them. While these flags are typically set at the application level, .htaccess offers a useful backstop for enforcing them at the server layer, especially on shared hosting where editing application code directly isn’t always practical.
What Each Cookie Flag Actually Protects Against
Before touching configuration, it helps to understand what each flag does.
- HttpOnly prevents JavaScript from reading the cookie via
document.cookie, which protects session tokens from being stolen through cross-site scripting (XSS) attacks
- Secure ensures the cookie is only sent over HTTPS connections, reducing the risk of interception on unencrypted networks — this matters even on sites using HSTS, since a single misconfigured endpoint or redirect can still leak sensitive data
- SameSite controls when the browser sends the cookie with cross-site requests, protecting against CSRF attacks.
Strict blocks the cookie from being sent with any cross-site request for the strongest protection, while Lax still allows the cookie on top-level navigations like clicking a link, offering a more practical default that avoids breaking normal navigation flows
Setting Cookie Security Flags via .htaccess
Apache allows you to modify the Set-Cookie header directly through .htaccess using mod_headers, which is useful when you want to enforce flags server-wide rather than relying on every script or framework to set them correctly.
<IfModule mod_headers.c>
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;SameSite=Strict
</IfModule>
This appends HttpOnly, Secure, and SameSite=Strict to every Set-Cookie header the server sends, regardless of whether the underlying application code included them. It’s a solid safety net, though it’s worth testing thoroughly, since applying SameSite=Strict globally can break login flows or third-party integrations that rely on cross-site cookie behavior.
A More Targeted Approach
If you only want to enforce these flags on specific cookies rather than all of them, you can scope the rule with a conditional match:
<IfModule mod_headers.c>
Header edit Set-Cookie "^(PHPSESSID=.*)$" "$1;HttpOnly;Secure;SameSite=Lax"
</IfModule>
This targets a specific cookie name (in this case, PHP’s default session cookie) rather than rewriting every cookie the server sends, giving more granular control when different cookies need different security levels.
Enforcing HTTPS Alongside Cookie Security
Since the Secure flag only matters if your site actually enforces HTTPS, it’s worth pairing your cookie rules with an HTTPS redirect in the same .htaccess file:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Without this, the Secure flag will still function correctly, but any request that does reach your server over plain HTTP won’t have the cookie attached at all, which can cause confusing session issues rather than an actual security gap.
Choosing the Right SameSite Value
The correct SameSite setting depends heavily on how your site’s frontend and backend are structured. If your frontend and API share the same parent domain — for example, app.example.com and api.example.com — the backend needs to explicitly set the cookie’s Domain attribute to the shared parent domain for the cookie to be usable across both, and SameSite=Lax or Strict will still work correctly since the browser treats these as same-site requests. If cookies genuinely need to be sent across unrelated domains, SameSite=None is required, but it must always be paired with the Secure flag — modern browsers will silently drop the cookie entirely if SameSite=None is set without Secure.
Using the __Secure- and __Host- Prefixes
For an extra layer of enforcement beyond what flags alone provide, cookie name prefixes give guarantees the browser actually enforces at the platform level, rather than relying on every code path to set flags correctly.
- __Secure- prefix requires the cookie to have the Secure flag and be set from a secure (HTTPS) origin
- __Host- prefix goes further, requiring Secure, no Domain attribute, and a Path of
/, effectively locking the cookie to the exact host that set it
These prefixes can also be enforced through the same mod_headers rewrite approach if your application doesn’t already implement them.
Common Mistakes Worth Avoiding
A handful of misconfigurations show up repeatedly in real-world audits.
- Shipping cookies without the Secure flag in production, even on otherwise HTTPS-only sites
- Setting
SameSite=None without Secure, which causes modern browsers to silently reject the cookie entirely
- Inconsistent configuration where the main login route sets the correct flags, but a forgotten admin or legacy endpoint doesn’t
- Storing sensitive data like JWTs or tokens in localStorage instead of an HttpOnly cookie, leaving them exposed to any successful XSS attack
- Not testing on Safari and mobile webviews specifically, since they have their own quirks around SameSite and caching behavior that can behave differently from desktop Chrome or Firefox
Verifying Your Configuration
After applying .htaccess rules, it’s worth confirming they’re actually working rather than assuming they are. Open your browser’s developer tools, go to the Application tab, and check that HttpOnly, Secure, and SameSite are present and correctly set on each cookie. If possible, try loading your site over plain HTTP during testing to confirm that Secure cookies genuinely aren’t being sent under those conditions.
Join The Discussion
Cookie security is one of those settings that’s easy to get wrong quietly, since a missing flag rarely breaks anything visibly until it’s actually exploited. Have you used .htaccess to enforce cookie flags server-wide, or run into SameSite issues with third-party integrations breaking unexpectedly? Share your experiences, configurations, or questions below, especially if you’ve found edge cases that tripped up your setup.