Guide · 3 min read

Secure, HttpOnly and SameSite: fix cookie flags

Short answer: session and login cookies should have Secure (only sent over HTTPS), HttpOnly (not readable by JavaScript, which limits damage from XSS) and SameSite=Lax or Strict (not sent on most cross-site requests, which helps against CSRF). Set them where the cookie is created, or add them at the web server as a fallback.

Updated · HackGrade

See what your site sends

Terminal
curl -sI https://yourdomain.com | grep -i '^set-cookie'

PHP sessions

php.ini
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"
PHP 7.3+
setcookie('name', $value, [
    'expires'  => time() + 3600,
    'path'     => '/',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

WordPress

WordPress login cookies are already HttpOnly. Make sure the admin is HTTPS-only so they are also Secure:

wp-config.php
define( 'FORCE_SSL_ADMIN', true );

Cookies set by plugins are up to each plugin. If one is missing flags, check for an update or use the server fallback below.

nginx (as a reverse proxy)

nginx 1.19.3+
proxy_cookie_flags ~ secure httponly samesite=lax;

Apache

Apache mod_headers
Header edit Set-Cookie "^((?!.*;\s*Secure).*)$" "$1; Secure"
Header edit Set-Cookie "^((?!.*;\s*HttpOnly).*)$" "$1; HttpOnly"
Header edit Set-Cookie "^((?!.*;\s*SameSite).*)$" "$1; SameSite=Lax"

If the cookies come from a proxied backend (for example PHP-FPM behind ProxyPass) and nothing changes, use Header always edit instead.

Don’t break things

  • Cookies that your own JavaScript must read (some consent banners, A/B tests) can’t be HttpOnly.
  • SameSite=None is needed for cookies used inside third-party iframes, and it requires Secure.
  • SameSite=Strict can log users out when they arrive from a link in an email. Lax is the safer default.

Frequently asked questions

Do analytics cookies need these flags?

Secure, yes, on an HTTPS site. HttpOnly only if no script needs to read them, which is rarely the case for analytics.

Is SameSite=Lax enough against CSRF?

It blocks most cross-site form posts, but forms that change data should still use CSRF tokens.

Why does a scanner still report a missing flag after my fix?

Often a page cache or CDN is serving the old response, or a plugin sets its own cookie. Purge the cache and check the Set-Cookie lines again.

Sources

Related guides

Free · Instant · No sign-up

Find out your grade before someone else does.

Passive scan. Nothing is attacked without your permission.

Free to start. Paid plans from $12 a month, or a one-time report for $29. See pricing