Cookie security: protecting the thing that keeps customers logged in
Three small flags on a cookie decide whether a logged-in session can be stolen. What Secure, HttpOnly and SameSite each do.
The short version
Secure, HttpOnly and SameSite) decide how hard it is for someone else to get hold of it. Most sites set some of them. Few set all three.Why the session cookie is the crown jewel
Passwords are checked once, at login. After that, your site hands the browser a session cookie — effectively a temporary key — and trusts anyone who presents it. That is convenient and universal, but it means an attacker does not need to steal a password; stealing the cookie is just as good and often easier. The cookie flags exist to make each of the known ways of stealing it fail.
Secure: never send it in the clear
Without the Secure flag, the browser will send the cookie over plain HTTP as well as HTTPS. So if a visitor ever loads a single http:// page of your site — by typing the address, from an old link, or because an attacker on the same Wi-Fi nudged them there — the cookie travels unencrypted and can be read off the network. With Secure, the browser simply never sends it except over HTTPS.
If your whole site is HTTPS (and it should be — see our guide to forcing HTTPS), there is no downside to this flag at all.
HttpOnly: keep it away from scripts
Normally, JavaScript running on your page can read your cookies. That means the classic injected-script attack (cross-site scripting, or XSS — the thing a Content Security Policy guards against) can grab the session cookie and send it to the attacker, who then logs in as your customer.
The HttpOnly flag makes the cookie invisible to JavaScript. The browser still sends it with every request, so your site works exactly as before, but a script on the page cannot read it. It turns a successful XSS attack from "steal every logged-in user" into something far less damaging. There is almost never a reason for a session cookie to be readable by scripts.
SameSite: do not send it on requests from other sites
Here is a subtle one. If a visitor is logged in to your site and then visits an attacker's page, that page can make their browser send a request *to your site* — submitting a form, say — and the browser attaches the session cookie automatically. Your site sees a valid logged-in request and acts on it: changes their email address, places an order, transfers money. The visitor clicked nothing on your site. This is cross-site request forgery (CSRF), and for years every web application needed special tokens to defend against it.
The SameSite flag tells the browser when to include the cookie on requests that *originate from another site*:
SameSite=Strict— never include it on cross-site requests. Strongest, but a visitor following a link to your site from an email will arrive logged out until they refresh.SameSite=Lax— include it when the visitor navigates to your site by clicking a link, but not on hidden form posts or background requests from other sites. This is the sensible default and blocks the attack described above.SameSite=None— always include it (requiresSecure). Only needed for cookies that genuinely must work inside other sites' pages, such as embedded widgets or payment frames.
Modern browsers treat a cookie with no SameSite attribute as Lax by default, but that behaviour varies and is not something to rely on — set it explicitly.
Cookie scope: not too wide
A cookie also has a Domain. Set to yourbusiness.co.uk, it is sent to every subdomain — shop., blog., old-test-site. — so a weakness on any one of them exposes the cookie for all of them. Unless you specifically need the login to work across subdomains, leave Domain out so the cookie is sent only to the exact host that set it. We flag session cookies scoped to the whole domain, particularly when the domain has many subdomains.
How to set the flags
In PHP, the cleanest approach is in the session configuration, before session_start():
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_samesite', 'Lax');
session_start();
For other cookies you set yourself, use the options array form of setcookie():
setcookie('remember', $token, [
'expires' => time() + 86400 * 30,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
WordPress sets HttpOnly by default and Secure when the site URL is HTTPS; a security plugin can add SameSite. On cPanel/Apache hosting, a blanket fallback for anything the application misses is possible in .htaccess: Header always edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Lax" — but it is a blunt tool, and will break any cookie that genuinely needs to be read by JavaScript, so prefer fixing the application.
What VigilScan checks
We look at every cookie your homepage sets, identify which look like session or authentication cookies (by name and shape), and check each for Secure, HttpOnly and SameSite. We also check whether the Domain is set wider than it needs to be. The report shows the cookie name and the exact attributes we saw, so you can match it to your code.
Common questions
My cookie banner plugin sets cookies without these flags. Does that matter?
Will SameSite=Lax break "login with Google" or payment redirects?
If I set all three flags, do I still need CSRF tokens?
Not sure if this affects your site?
Run a free check — it takes about 90 seconds and tells you exactly where you stand.