# How do I share a login session across multiple domains?

> You can’t share a session cookie between two different domains, because browsers only send a cookie to the domain that set it. Instead, each domain keeps its own session and gets it from one central login, an OpenID Connect provider. Someone who is already signed in there is sent straight back with a one-time code, without seeing a form.

You run more than one site, like `app.example.com`, `example.net` and `shop.example.org`, and you want someone who signs in on one of them to be signed in on the others. The obvious idea is to share the session cookie. That works for subdomains, but not for different domains, and no setting changes that. What you share instead is the **sign-in**: every domain asks the same place who the person is.

## Why can’t two domains share a session cookie?

A cookie belongs to the site that set it. When `example.com` sets `session=abc`, the browser sends that cookie back to `example.com` and, if the cookie has `Domain=example.com`, to its subdomains. It never sends it to `example.net`. This rule is what stops any website from reading your bank’s session cookie, so browsers will not relax it.

Third-party cookies used to offer a workaround: an `example.net` page could load something from `example.com` and get that domain’s cookies with it. Safari and Firefox block third-party cookies by default, and storage inside iframes is now partitioned by the top-level site. Tricks built on hidden iframes or `postMessage` between your domains break for many of your users, often without an error.

## What are your options?

There are three ways to sign someone in on more than one site. Which one fits depends on whether your sites share a parent domain.

| Approach | Works across different domains? | What you build |
| --- | --- | --- |
| One cookie for the parent domain | No, only for subdomains like `app.example.com` and `shop.example.com` | Nothing extra: set `Domain=example.com` on the session cookie |
| A central login (identity provider) | Yes, any number of domains | A redirect route and a callback route on each site |
| Hand a token from one site to the other | Yes, but you secure it yourself | One-time tokens, expiry, replay protection, redirect checks |

**One parent-domain cookie** is the simplest when it fits. Everything under `example.com` can read a cookie set with `Domain=example.com`. It stops at the edge of that domain.

**Handing tokens across** means site A creates a short-lived token and redirects to site B with it in the URL, and site B trades it for a session. It works, but you end up writing a small identity provider: tokens that work once, expire fast, are bound to the target site, and never land in logs. Most teams who start here move to a real provider by the third domain.

**A central login** is the standard answer, and it is what “single sign-on” means on the web. One service, the identity provider, keeps its own session on its own domain. Every one of your sites sends people there to sign in and gets them back with a one-time code.

## How a central login shares the sign-in

The protocol most providers speak is OpenID Connect (OIDC), a layer on top of OAuth 2.0 that tells your app who signed in. Here is what happens when someone who signed in on `app.example.com` opens `example.net` for the first time:

1. `example.net` has no session for them, so it redirects the browser to the provider, with its own address as the return address.
2. The provider sees its own session cookie, set when they signed in on `app.example.com`. It doesn’t show a form.
3. The provider sends the browser straight back to `example.net` with a code that works once.
4. The server of `example.net` exchanges the code for the person’s identity, then sets its **own** session cookie.

The person sees a short redirect and is signed in. Each domain still has its own session, and nothing crosses between domains except the browser itself, carrying a one-time code. That is why this works without third-party cookies.

> [!TIP]
> To check for a sign-in without ever showing a page, add `prompt=none` to the redirect. The provider answers at once: with a code if the person is signed in, or with `error=login_required` if not.

## What to watch out for

- **Check the return address.** The provider must only send codes to addresses you registered. A provider that accepts any return address hands sign-in codes to whoever asks.
- **Exchange codes on your server.** The code is worth nothing without your app’s secret, so the exchange belongs on the server, not in the browser.
- **Use `state` and PKCE.** `state` ties the callback to the browser that started the sign-in, and PKCE stops a stolen code from being used.
- **Sign-out is per domain.** Signing out of one site clears that site’s session. To end the central session too, send the browser to the provider’s logout address.
- **Don’t merge accounts by email** unless the provider has verified the address. Use a stable user id (`sub` in OpenID Connect).

## How loginos does it

loginos is an OpenID Connect provider built for exactly this: one product on many domains. You create a **platform**, add your domains (as many as you have), and they all share one pool of people. Someone who creates an account on one domain is already a member on the others.

On each domain you add two routes: one that redirects to loginos, and a callback that exchanges the code. The callback path is the same on every domain, `/api/auth/login/single/callback` by default, and loginos only sends people back to `https://<one of your domains><callback path>`.

```js title="Express"
const LOGINOS = "https://loginos.io";

// The callback, on every one of your domains.
app.get("/api/auth/login/single/callback", async (req, res) => {
  const [state, verifier] = (req.cookies.lo_flow ?? "").split(".");
  if (!state || req.query.state !== state) return res.status(400).send("Sign-in expired, try again.");

  const r = await fetch(`${LOGINOS}/oauth/token`, {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code: String(req.query.code ?? ""),
      redirect_uri: `https://${req.get("host")}/api/auth/login/single/callback`,
      code_verifier: verifier,
      client_id: process.env.LOGINOS_APP_KEY,
      client_secret: process.env.LOGINOS_APP_SECRET,
    }),
  });
  if (!r.ok) return res.status(401).send("Sign-in failed.");
  const { user } = await r.json(); // sub, preferred_username, roles, permissions…
  req.session.user = user; // this domain's own session
  res.redirect("/");
});
```

The [quickstart](https://loginos.io/docs/quickstart.md) has the redirect route and complete versions for Next.js, Express and PHP. A few things come with it:

- **No form on the second domain.** Someone signed in to loginos who has used your platform before comes straight back.
- **Domains that come and go.** Add domains in the console or [from your server](https://loginos.io/docs/domains.md#from-your-server), cover subdomains with a verified [wildcard](https://loginos.io/docs/domains.md#wildcards), or let your server confirm [new domains](https://loginos.io/docs/domains.md#automatic-domains) as people sign in.
- **No redirect at all, if you like.** Chrome and Edge can show their own [“Continue as” prompt](https://loginos.io/docs/continue-as.md) on your page.
- **Passkeys**, and [roles and permissions](https://loginos.io/docs/roles.md) in every token, on every domain.

## Frequently asked questions

### Can I share cookies between example.com and example.net?

No. Browsers send a cookie only to the domain that set it and, with the `Domain` attribute, to that domain’s subdomains. `example.com` and `example.net` are different sites, so each needs its own session, issued after one central sign-in.

### Do subdomains count as different domains?

Not for cookies. A cookie set with `Domain=example.com` is sent to `app.example.com` and `shop.example.com` too. If all your sites are subdomains of one domain, a parent-domain cookie can be enough. As soon as you add a second domain, you need a central login.

### Will people have to sign in again on my second domain?

Not with a central login. When they reach the second domain, it redirects them to the provider, which recognizes them from its own session and sends them back with a code. They see a quick redirect, not a form.

### Does this still work when third-party cookies are blocked?

Yes. The redirect flow only uses first-party cookies: the provider reads its own cookie on its own domain, and your site sets its own cookie on yours. It doesn’t depend on iframes or third-party storage.

### What happens when someone signs out?

Each site clears its own session. The provider’s session stays until it ends or the person signs out there, so they may come back without a form. If you want sign-out to end everything, also send the browser to the provider’s logout address.

---

Updated 2026-10-14. Web page: https://loginos.io/answers/share-login-session-across-domains
