# Third‑party cookies and session restoration

{% hint style="success" %}

> **You’re safe by default** Even in the worst‑case scenario where your authorization server’s cookies are blocked by the browser, `oidc‑spa` automatically falls back to a near‑seamless full‑page redirect. **No configuration required.**
>
> That said, if you want the **best possible user experience**, it’s worth understanding what’s going on under the hood and configuring your domains and headers accordingly.
> {% endhint %}

This page explains why modern browsers often refuse to send cookies in third‑party contexts, how that impacts silent session restoration in a frontend-centric auth model, and how to configure your domain and security headers so that `oidc‑spa` can deliver a seamless UX.

> TL;DR
>
> 1. Align your application and authorization endpoint under a common parent domain (same site) so the browser treats your IdP as first‑party to your app.
> 2. Prefer iframe‑based restoration when possible, it's enabled by default when your IdP is on the same site as your app.
> 3. If your CSP completely forbids iframes and you have no way to tweak them or if the IdP must live on a foreign domain, explicitly use full‑page redirects.

***

### Why third‑party cookies matter here

Traditional web apps keep a session on your backend. Your browser sends the backend’s own cookies on every request, so restoring the user session is trivial.

With `oidc‑spa`, your frontend talks directly to the authorization server. When a user revisits your app, `oidc‑spa` first tries to learn whether the user still has a valid session **at the IdP** without prompting for credentials again. It does so by contacting the authorization endpoint silently. If the browser **sends the IdP’s cookies** in that context, the IdP can attest that the user is still signed in and return the data needed to rebuild local identity.

If the browser considers the IdP **third‑party** to your app, it often refuses to attach those cookies in an embedded context (iframe). oidc-spa has to use full‑page redirect in those configurations. It happens so fast that it's hardly perceivable, however iframe session restoration still yields the best performance.

***

### Make your IdP first‑party: share a parent domain

The key is to host your application and your authorization endpoint under the **same registrable (parent) domain - this is commonly referred to as "same site"**.

#### ✅ Examples where the IdP is *not* third‑party - same site

* App: `www.my-company.com`, `dashboard.my-company.com`, or `my-company.com/dashboard`
* Authorization endpoint: `https://auth.my-company.com/realms/oidc-spa/protocol/openid-connect/auth`
* **Parent domain:** `my-company.com`

#### ❌ Examples where the IdP *is* third‑party

* App: `my-company.com`
* Authorization endpoints on unrelated domains:
  * `https://my-keycloak.com/realms/oidc-spa/protocol/openid-connect/auth` *(configurable; you choose where to host)*
  * `https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize` *(configurable via External ID / B2C custom domain)*
  * `https://<tenant>.us.auth0.com/authorize` *(configurable via Auth0 Custom Domains)*
  * `https://accounts.google.com/o/oauth2/v2/auth` *(not configurable)*

***

### How `oidc‑spa` restores sessions

`oidc‑spa` supports two session restoration strategies. You choose (or let the library auto‑choose) using `sessionRestorationMethod`.

```ts
bootstrapOidc({ // or createOidc({
  // "auto" (default) | "iframe" | "full page redirect"
  sessionRestorationMethod: "auto"
});
```

#### "iframe" (silent, seamless)

* The app opens an **invisible iframe** to the authorization endpoint with parameters that request a silent check.
* If the IdP’s cookies are present, the IdP returns enough data for the app to rebuild identity without leaving the page.
* **Best UX.** Requires that the browser can send IdP cookies in the iframe and that your app’s security headers allow same‑origin iframes.

#### "full page redirect" (silent but navigational)

* The app performs a quick top‑level redirect to the authorization endpoint, which always carries IdP cookies.
* The redirect returns immediately to your app with the information needed to rebuild identity.
* **Works everywhere** but is about 30% slower and the url flashes auth response info briefly.
* **Multiple OIDC clients in one page:** to avoid a redirect loop, the app may need to persist state between reloads (for example, tokens or a minimal session hint) which weakens the “no persistence” posture.

#### "auto" (default and recommended)

* `oidc‑spa` selects the best method at runtime. If your app and IdP share a parent domain and iframes are permitted, it uses "iframe". Otherwise it uses "full page redirect".

> **Migration note**
>
> The old `noIframe` option is **deprecated**. Use `sessionRestorationMethod: "full page redirect"` to get equivalent behavior.

***

### When iframes are blocked by your CSP

In that case, the question is:

**Are you in control of your server configuration, can you change the HTTP response headers?**

{% tabs %}
{% tab title="Yes" %}
If you can edit your server config, then you can relax your CSP just enough to allow iframe in the context of SSO:

{% content-ref url="csp-configuration" %}
[csp-configuration](https://docs.oidc-spa.dev/resources/csp-configuration)
{% endcontent-ref %}
{% endtab %}

{% tab title="No" %}
If your server is what it is and you have no control over it, then your only option is to force `oidc-spa` to use full page redirect to restore the user's session:

```ts
sessionRestorationMethod: "full page redirect"
```

{% endtab %}
{% endtabs %}

### Local development

When your app runs on `localhost` and your IdP lives on a different domain, which is almost always the case unless you run Keycloak locally.

The browser treats the IdP as third‑party so oidc-spa will fallback to full page redirect. To run your app in development like you would in prod you need to:

1. Set `sessionRestorationMethod: "iframe"` explicitly to force oidc-spa to use iframe.
2. Allow third-party cookies in localhost:

<figure><img src="https://content.gitbook.com/content/8rg9Xp0uGe5G6xs1sCAG/blobs/YTgAHSklSHgvalDOCAdS/image.png" alt="" width="348"><figcaption></figcaption></figure>

***

### SaaS IdPs and custom domains

Most managed IdPs let you put their endpoints behind your domain. This is crucial to avoid third‑party treatment.

* **Auth0**: supports **Custom Domains** for the authorization and token endpoints.
* **Microsoft Entra External ID / Azure AD B2C**: supports **Custom URL domains** for user flows and policies, which cover the authorization endpoint.
* **Clerk**: supports **custom and satellite domains** and proxying its Frontend API so your app interacts under your domain.
* **Google**: the authorization endpoint is always `accounts.google.com`. You cannot host it under your domain. If you need Google login, consider fronting multiple IdPs with an aggregator like Keycloak, Auth0 or Clerk that itself lives under your domain.

***

### UX comparison

A short video that shows the UX difference between iframe‑based restoration and a full‑page redirect:

(This video was recorded a while ago, performance is **much** better now)

{% embed url="<https://www.youtube.com/watch?v=55sZ7XSWh4Q>" %}

***

### Decision guide

1. Can your app and IdP share a parent domain?
   * **Yes** → Use `sessionRestorationMethod: "auto"` (will pick "iframe").
   * **No** → Use `"full page redirect"`.
2. Do your security headers allow self‑iframes?
   * **Yes** → You are set.
   * **No** → Either relax to `frame-ancestors 'self'` or stick to `"full page redirect"`.
3. Do you host multiple OIDC clients in one page?
   * **Yes** → Strongly prefer iframe restoration to avoid redirect loops and persistence.

***

### API reference

```ts
/**
 * Controls how session restoration is handled.
 * "auto" picks the best strategy at runtime.
 */
sessionRestorationMethod?: "iframe" | "full page redirect" | "auto";

/**
 * @deprecated Use `sessionRestorationMethod: "full page redirect"` instead.
 */
noIframe?: boolean;
```
