Secure Cookie Flags HttpOnly Secure SameSite for NZ sites: Set-Cookie attributes explained

Quick answer

Every cookie your site sets should carry three flags: Secure (only ever sent over HTTPS), HttpOnly (not readable by JavaScript), and SameSite=Lax (not sent on cross-site requests). Together they stop the three common ways cookies are stolen or replayed in cross-site request forgery (CSRF) attacks. A scanner cookies finding from the Free Website Security Check flags any of these as missing — paste the snippet for your stack and restart the service.

If you are running your site behind a CDN or reverse proxy, set the flags at the application layer (where the cookie is created); setting them at the proxy usually does not work for Set-Cookie headers (1).

What cookies do and what the flags protect

A cookie is a short piece of state your server asks the browser to remember. On every subsequent request, the browser sends it back. This is how your site knows who you are across page loads.

Without flags, a cookie is just a string. With the right three flags, it becomes much harder to steal or misuse:

| Flag | What it stops | Plain-English behaviour | |---|---|---| | Secure | Cookie theft over plain HTTP | The browser refuses to send the cookie unless the connection is HTTPS | | HttpOnly | Cookie theft via XSS (cross-site scripting) | JavaScript on the page cannot read the cookie via document.cookie | | SameSite=Lax (or Strict) | CSRF (cross-site request forgery) | The browser does not send the cookie when the request originates from a different site |

A reasonable baseline for a NZ school or small-business site is to ship all three. The next sections explain what each flag does in detail and why it matters in NZ context.

The Secure flag

Secure tells the browser: never send this cookie back unless the connection is HTTPS. Without it, a coffee-shop network attacker can briefly force the visitor over plain HTTP and read the cookie in transit (1).

Secure does not require HSTS to be set, but HSTS makes it much harder to bypass — once a browser has seen HSTS, it silently rewrites any http:// URL to https://, which means a downgrade attack on a Secure cookie becomes almost impossible. See the HSTS guide for the connection-side details.

Secure is not the same as the padlock icon in the browser address bar. The padlock only tells you the page itself loaded over HTTPS. A cookie without the Secure flag can still be sent over plain HTTP on the next request, even if the page that set it was loaded over HTTPS (1).

The HttpOnly flag

HttpOnly tells the browser: do not let JavaScript on the page read this cookie. Without HttpOnly, any script running on your page — including a malicious script injected via a cross-site scripting (XSS) bug — can read the cookie and send it to an attacker (2).

For session cookies and authentication tokens, the right default is HttpOnly. There is almost never a legitimate reason for a page script to read these cookies; the cookie is for the server, not the client.

If your front-end JavaScript legitimately needs to read a cookie (for example, to display the logged-in user's name), use a separate, non-sensitive cookie without HttpOnly and keep the session cookie protected.

The SameSite attribute

SameSite tells the browser when the cookie should be sent on cross-site requests. The valid values are Strict, Lax, and None:

  • SameSite=Strict — never sent on any cross-site request, including top-level navigations
  • SameSite=Lax — sent on top-level navigations (clicking a link), but not on subresource requests (images, iframes, fetch from a third party)
  • SameSite=None — always sent; must also set Secure

Lax is the right default for most NZ sites. It blocks the common CSRF attack (a malicious site triggering a request to your bank on behalf of a logged-in user) without breaking the user experience of clicking through to your site from email or a search result (1).

Browsers default to SameSite=Lax for cookies that have no SameSite attribute at all, but only as of recent versions. Older browsers, embedded webviews, and some school MIS portals still send cookies without it. Setting it explicitly is the only way to be sure (1).

Why this matters for New Zealand sites under the Privacy Act 2020

Cookies carry identity. A stolen session cookie is functionally equivalent to a stolen password — the attacker becomes the user until the cookie expires or is revoked. That identity often links back to student data, staff records, or payment details, all of which sit inside the Privacy Act 2020 (3).

Two specific NZ angles:

  • School portals and student records. A school portal that handles enrolments, attendance, or pastoral notes uses cookies for the login session. If those cookies lack Secure and HttpOnly, a single XSS bug anywhere on the school site is enough to exfiltrate them. That is a notifiable privacy breach under the Act (3).
  • Public-sector SaaS contracts. If your site vendors are processing data on behalf of a school, their cookie handling is part of your due diligence. CERT NZ names session management as a baseline control (4).

For the broader educator context (devices, accounts, phishing), see Building Cyber-Safe Habits as an NZ Educator.

Copy-paste fixes for the four common stacks

The exact way to set the flags depends on how your site creates cookies. The snippets below cover the most common stacks.

This is the most common case for modern NZ small-business sites:

import cookieParser from 'cookie-parser';

app.use(cookieParser());

app.get('/login', (req, res) => {
  res.cookie('session', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    path: '/',
    maxAge: 60 * 60 * 1000, // 1 hour
  });
  res.send('logged in');
});

If you are using Express session middleware (express-session), set the flags there instead:

app.use(session({
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 60 * 60 * 1000,
  },
  // ... other session config
}));

Django

In settings.py:

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = 'Lax'

These four settings cover the session cookie and the CSRF token cookie, which together handle almost all authentication on a Django site.

nginx (reverse proxy)

You usually cannot set cookie flags in nginx directly — the application sets the Set-Cookie header and nginx just passes it through. The exception is proxy_cookie_path, which lets you tweak the path:

proxy_cookie_path / "/; Secure; HttpOnly; SameSite=Lax";

Note: this trick is brittle. It is better to set the flags in the application where the cookie is created. The snippets above for Express and Django are the reliable path.

Apache

Like nginx, Apache usually only passes cookies through. If you must rewrite them at the proxy level, mod_headers can do it:

Header always edit Set-Cookie "^([^;]+)(.*)$" "$1$2; Secure; HttpOnly; SameSite=Lax"

Treat this as a safety net, not a substitute for setting the flags at the application layer. Application-level configuration is auditable; rewrite rules are easy to miss in code review.

Secure Cookie Flags HttpOnly Secure SameSite for NZ sites — session protection and CSRF defence attributes

From running the free scanner across New Zealand school and small-business sites, the most frequent missteps are:

  • Setting Secure but no SameSite. Without SameSite, the cookie is still sent on cross-site requests, leaving CSRF open. Secure alone is not enough (1).
  • Setting SameSite=Strict on an SSO cookie. If your school uses single sign-on (Google Workspace, Microsoft 365), Strict can break the initial login from a third-party link. Lax is usually the safer default (1).
  • Forgetting Secure on the CSRF cookie. Django and similar frameworks set a separate CSRF cookie. If the session cookie is Secure but the CSRF cookie is not, the CSRF token can leak over plain HTTP (5).
  • Setting the flags at the proxy and not the app. Reverse-proxy rewrites are easy to forget. Set them at the application layer first; the proxy only as a defence-in-depth measure.
  • Not setting Path=/. If your cookie has a narrow Path (e.g. /admin), it may not be sent on requests outside that path, breaking login state. Use Path=/ unless you have a specific reason not to.

Verify before you ship

Two ways to confirm the flags are live:

1. Run the free NZAI scanner

The Free Website Security Check flags each cookie missing any of the three flags as a cookies finding, and tells you which snippet to paste.

2. Browser DevTools

Open your site in Chrome or Firefox, log in, press F12, switch to the Application tab, click Cookies in the left pane, and inspect each cookie. You should see Secure, HttpOnly, and SameSite all ticked.

3. curl from the command line

curl -sI https://yoursite.nz | grep -i set-cookie

You should see the flags appended to each Set-Cookie header. To bypass cache, append a fresh query string (?nocache=1) or test against a URL the cache has not seen.

Knowledge check

Test your understanding of secure cookie flags with these questions. Click or tap each card to flip it and see the answer.

Q1Your school portal sets a session cookie with the Secure flag but no HttpOnly or SameSite. The site has a minor XSS bug in the search box. What can an attacker actually do, and which flag would have stopped it?tap to flip
Answer: With XSS on the page, the attacker's injected script can run document.cookie and read the session cookie — then send it to an attacker-controlled server. The user is now logged in as the attacker until the cookie expires. The HttpOnly flag would have prevented this: the browser refuses to expose cookies marked HttpOnly to JavaScript, regardless of any XSS bug. Secure does not protect against XSS — it only protects against network sniffing. SameSite does not protect against XSS either. The right mitigation for XSS is HttpOnly for sensitive cookies plus a Content-Security-Policy header; see the CSP deep dive guide for the CSP side (1)(2).
Q2You set SameSite=Strict on every cookie. A parent clicks a link from their school newsletter email, lands on the school portal, and sees a logged-out page. What is happening?tap to flip
Answer: SameSite=Strict blocks the cookie on any cross-site request, including top-level navigations from a different site. The email link is a cross-site navigation (the email client is a different origin from the school portal), so the browser omits the cookie. The user lands on the portal but appears logged out. The fix is to use SameSite=Lax instead — Lax allows the cookie on top-level navigations (link clicks), which covers the email case, while still blocking CSRF on subresource requests (1).
Q3You set HttpOnly and SameSite=Lax on the session cookie at the Express layer. The site still fails the scanner cookies check with "missing Secure flag". The session config clearly says secure: true. What is going on?tap to flip
Answer: The most common cause is that the scanner (or the user, or your reverse proxy) hit the site over plain HTTP to test. Express's secure: true setting only marks the cookie as Secure when the underlying request was made over HTTPS — Express checks req.secure and the X-Forwarded-Proto header. If you are behind a proxy and have not set app.set('trust proxy', 1), Express does not trust the proxy header and treats the request as plain HTTP, refusing to set the Secure flag. The fix is to either trust the proxy or to set cookie.secure based on the environment (1).

Sources and references

[1] Mozilla. (2025). MDN Web Docs — Set-Cookie header. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie

[2] OWASP Foundation. (2025). Cross-Site Scripting (XSS) Prevention Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html

[3] New Zealand. Parliament. (2020). Privacy Act 2020. https://www.legislation.govt.nz/act/public/2020/0031/latest/whole.html

[4] CERT NZ. (2025). Critical controls for small organisations. https://www.cert.govt.nz/

[5] Mozilla. (2025). MDN Web Docs — SameSite cookies. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite

[6] Internet Engineering Task Force. (2025). RFC 6265 — HTTP State Management Mechanism. https://datatracker.ietf.org/doc/html/rfc6265

What to do next