Same-Origin Policy, CORS, and CSRF: What Actually Protects Your Login Flows
Updated · 11 min read · By Deepak Gupta
On this page
- What is the same-origin policy?
- What counts as the same origin?
- What the same-origin policy allows and blocks
- Why the same-origin policy does not prevent CSRF
- Where CORS fits (and where it does not)
- How to prevent CSRF in customer login and account flows
- Token-based APIs and OAuth redirects
- A quick CSRF checklist for CIAM teams
Key takeaways
- An origin is scheme + host + port. Two URLs share an origin only when all three match exactly.
- The same-origin policy stops scripts from reading cross-origin responses. It does not stop the browser from sending cross-origin requests.
- That gap is where CSRF lives: a forged request rides the victim's cookies even though the attacker never sees the response.
- CORS is an opt-in relaxation of the same-origin policy set by the server. It is never a CSRF defense on its own.
- SameSite=Lax cookies plus a CSRF token or an Origin / Sec-Fetch-Site check close CSRF for login, signup, and account settings.
What is the same-origin policy?
SOP was never designed to stop requests. It controls what a page can read. Browsers still let a page embed images, scripts, and stylesheets from anywhere, submit forms to anywhere, and navigate anywhere. That distinction between sending and reading is the source of most confusion about CSRF and CORS, so it is worth getting precise.
What counts as the same origin?
An origin is the triple of scheme, host, and port, as defined in RFC 6454. Two URLs have the same origin only when all three match. Paths, query strings, and fragments do not matter. When the port is omitted, the scheme's default port applies (80 for HTTP, 443 for HTTPS).
Compared against https://example.com/account:
| URL | Same origin? | Why |
|---|---|---|
https://example.com/settings?tab=mfa | Yes | Only the path and query differ |
https://example.com:443/login | Yes | 443 is the default HTTPS port |
http://example.com/account | No | Different scheme |
https://app.example.com/account | No | Different host |
https://example.com:8443/account | No | Different port |
https://example.co/account | No | Different host |
A different port is a different origin. That trips teams running an auth server on :8443 next to the app on :443: the browser treats them as strangers, so every call between them needs CORS.
What the same-origin policy allows and blocks
In practice for a CIAM deployment:
- Allowed: a page on
evil.examplecan submit a hidden form that POSTs tohttps://bank.example/transfer. The browser sends it. - Allowed: that page can load
<img src="https://bank.example/logout">. The browser sends that GET too. - Blocked: a script on
evil.examplecallingfetch("https://bank.example/api/me")cannot read the JSON that comes back. - Blocked: the page cannot read the DOM of an iframe showing
bank.example.
The old escape hatch document.domain, which let two subdomains relax SOP between themselves, is deprecated. Chromium stopped honouring the setter by default in version 115, so do not design new flows around it.
Why the same-origin policy does not prevent CSRF
A typical CSRF against a customer account looks like this:
- The victim is signed in to
shop.example, with a session cookie in the browser. - The victim opens a page on
evil.example. - That page auto-submits a hidden form to
https://shop.example/account/emailwith the attacker's address. - If the session cookie rides along and the server trusts it alone, the email changes. A password reset to the new address completes the account takeover.
The same pattern hits every sensitive identity endpoint: email and phone change, MFA enrolment and removal, connected-app consent, password change without re-authentication, and logout.
Login CSRF is the inverted version. The attacker forges a login request with their own credentials, signing the victim into the attacker's account. Anything the victim saves afterwards, including payment details, lands in an account the attacker controls. Login forms need CSRF protection too.
Where CORS fits (and where it does not)
The rules that matter for identity APIs:
- Simple requests are sent without asking. A GET, or a POST with a form-style content type, goes straight to the server. CORS only decides whether the calling script may read the answer.
- Other requests trigger a preflight. A JSON POST, a custom header, or methods like PUT and DELETE make the browser send an
OPTIONSrequest first. The real request is sent only if the server approves. - Credentials need an exact origin. To allow cookies, the server must return
Access-Control-Allow-Credentials: trueand name the exact origin inAccess-Control-Allow-Origin. The wildcard*is rejected for credentialed requests. - Never reflect the
Originheader blindly. Echoing any incoming origin back with credentials allowed hands every site on the internet read access to your authenticated API.
JSONP, the pre-CORS trick of wrapping data in a <script> callback, bypasses SOP entirely by design. Retire any JSONP endpoint that returns user data.
How to prevent CSRF in customer login and account flows
The OWASP CSRF Prevention Cheat Sheet is the reference. Applied to CIAM:
- SameSite cookies.
SameSite=Laxblocks the session cookie on cross-site POSTs and subresource loads while keeping top-level navigation working. UseStrictfor admin consoles. Chromium has treated cookies without the attribute as Lax by default since 2020, but set it explicitly: other browsers and embedded webviews differ. - Synchronizer or signed double-submit token. Put a per-session token in each form and verify it server-side. Most frameworks ship this. Sign double-submit tokens and bind them to the session, or a subdomain that can set cookies can forge them.
- Origin and Fetch Metadata checks. Reject state-changing requests whose
Originheader is not yours. Where supported, reject requests withSec-Fetch-Site: cross-siteon sensitive endpoints. Both are cheap and need no client changes. - Re-authentication for high-risk actions. Require a fresh password, passkey, or MFA check before email change, MFA removal, and payout changes. This also caps the damage from a stolen session. See token lifetime best practices.
- Protect the login and signup forms. Login CSRF and forced signup are real. Apply the same token or Origin check to unauthenticated forms.
SameSite works on the site boundary, not the origin boundary. app.example.com and blog.example.com are same-site, so a compromised or user-controlled subdomain can still send same-site requests. Keep user-generated content on a separate registrable domain.
Token-based APIs and OAuth redirects
Single-page apps that send an access token in an Authorization header, instead of relying on cookies, are not exposed to classic CSRF: the browser never attaches that header automatically. The trade-off is that a token readable by JavaScript is exposed to XSS. The session management guide covers why HttpOnly cookies remain the default for browser sessions.
OAuth and OIDC redirect flows have their own CSRF surface: an attacker can inject their own authorization code into the victim's callback. The defenses are the state parameter, bound to the user's session, and PKCE, which OAuth 2.1 makes mandatory for public clients. The OAuth 2.1 guide walks through both.
A quick CSRF checklist for CIAM teams
- Session cookies are
Secure,HttpOnly, andSameSite=Laxor stricter. - Every state-changing endpoint, including login, signup, and logout, checks a CSRF token or the
Originheader. - No GET request changes state.
- CORS allowlists exact origins; no reflected origins with credentials; no JSONP.
- Email, phone, password, MFA, and payout changes require recent re-authentication.
- OAuth callbacks validate
stateand use PKCE. - User-generated content lives on a separate registrable domain.
Related guides
Session Management: JWTs vs Opaque Tokens, and How to Pick
JWT-based and opaque-token sessions trade off scale against revocability, the 2026 default is hybrid. Patterns, revocation, and where each is the right answer.
OAuth 2.1 Explained: What Changed and Why It Matters
OAuth 2.1 consolidates fifteen years of OAuth 2.0 practice into a single coherent specification. What it deprecates, what it requires, and how to migrate existing OAuth 2.0 code.
Account Takeover Defense: A Layered Approach for 2026
ATO is the single largest CIAM threat in 2026. The defense stack is layered, credential stuffing protection, MFA, session management, and recovery design, each addressing a different attack class.
Token Lifetime Best Practices: Access, Refresh, ID, and Session Tokens in 2026
How to set access, refresh, ID, and session token lifetimes for CIAM in 2026, the trade-offs, the defaults that work, and the patterns that fail in production.
Where to next
FAQ
- What is the same-origin policy?
- The same-origin policy is a browser rule that stops a script loaded from one origin from reading data returned by another origin. An origin is the scheme, host, and port of a URL. The policy keeps a malicious page from reading your bank session, inbox, or account settings through your logged-in browser.
- What counts as the same origin?
- Two URLs share an origin only when scheme, host, and port all match. https://example.com and https://example.com:443 are the same origin because 443 is the default HTTPS port. http://example.com, https://app.example.com, and https://example.com:8443 are each a different origin from https://example.com.
- Does the same-origin policy prevent CSRF?
- No. The same-origin policy blocks reading cross-origin responses, but browsers still send cross-origin form posts, image loads, and navigations, and they attach cookies to them. A CSRF attack only needs the request to arrive, not the response. Defend with SameSite cookies plus a CSRF token or Origin header check.
- Does CORS protect against CSRF?
- No. CORS tells the browser which other origins may read a response. A cross-origin form post needs no CORS permission at all, so a missing CORS header does not stop it from reaching your server. A misconfigured CORS policy can make CSRF worse by letting the attacker read the response too.
- What is the difference between same-origin and same-site?
- Same-origin compares scheme, host, and port exactly. Same-site compares the scheme and the registrable domain, so https://app.example.com and https://login.example.com are same-site but cross-origin. SameSite cookies use the site boundary, which means a compromised sibling subdomain can still send same-site requests.
- What is login CSRF?
- Login CSRF forges a login request that signs the victim into the attacker's account. Anything the victim then saves, such as a card number or search history, lands in an account the attacker controls. Protect the login form with a CSRF token or Origin check, just like any other state-changing form.
Sources
- MDN Web Docs, Same-origin policy: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
- RFC 6454, The Web Origin Concept: https://www.rfc-editor.org/rfc/rfc6454
- OWASP Cross-Site Request Forgery Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- MDN Web Docs, Cross-Origin Resource Sharing (CORS): https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- MDN Web Docs, Set-Cookie SameSite attribute: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
- W3C Fetch Metadata Request Headers: https://www.w3.org/TR/fetch-metadata/