Skip to content

Commit ace0af9

Browse files
authored
@W-22419797@ @W-22457820@ Docs for http-only (#3841)
* Apply configurable domain to cookies created by slas proxy * Lint * Add initial docs for http-only * Apply suggestions * Apply suggestions
1 parent d88a7c2 commit ace0af9

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

docs/httponly-storage-mapping.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# HttpOnly Mode: SLAS Property Storage Mapping
2+
3+
> **WIP: HttpOnly session cookies are in-progress. Do not enable `enableHttpOnlySessionCookies` in production.**
4+
>
5+
> **Scope: PWA Kit only.** This reference applies to projects built on PWA Kit and the cookie/localStorage routing performed by `commerce-sdk-react` and the SLAS proxy in `pwa-kit-runtime`. **It does not apply to sfNext (sf-next).** sfNext is HttpOnly by default and not every cookie listed here is present there.
6+
7+
Map authentication and session data storage locations when migrating PWA Kit from localStorage to HttpOnly cookies. Enabling `enableHttpOnlySessionCookies` moves sensitive tokens—access tokens, refresh tokens, and IDP credentials—from client-accessible localStorage to HttpOnly cookies that protect against XSS attacks. Use this reference to identify where each SLAS property lives in both non-HttpOnly and HttpOnly modes, and which cookies gain the HttpOnly security flag.
8+
9+
## Overview
10+
11+
When `enableHttpOnlySessionCookies` is turned on in a PWA Kit project, several SLAS-related auth properties stop being written to `localStorage` and start being read from proxy-set cookies instead. This doc is the reference for **where every property lives in each mode**.
12+
13+
For request-flow diagrams (token acquisition, refresh, logout, SCAPI proxying) see [HttpOnly Session Cookies — Architecture](./httponly-cookies-architecture.md).
14+
15+
For configuring a shared cookie domain across PWA Kit and SFRA in a hybrid deployment, see [Hybrid Cookie Domain Configuration](./hybrid-cookie-domain-configuration.md).
16+
17+
## How storage is selected
18+
19+
In **non-HttpOnly mode** (the default), `commerce-sdk-react` writes most SLAS response fields to `localStorage`. A few values (refresh tokens, `usid`) are already cookies because they're shared with SFRA in hybrid setups.
20+
21+
In **HttpOnly mode**, the SLAS proxy in `pwa-kit-runtime` sets cookies on every token response and strips the same fields from the JSON body. `commerce-sdk-react` then reads those values back from cookies. The cookie is the single source of truth—no `localStorage` mirror is kept.
22+
23+
All cookie names are suffixed with `_{siteId}` (for example `cc-at_RefArch`). The `{siteId}` suffix is omitted from the table below for readability.
24+
25+
## Reference table
26+
27+
| SLAS property | Non-HttpOnly source | HttpOnly source | HttpOnly flag (HttpOnly mode) | Notes |
28+
| --- | --- | --- | --- | --- |
29+
| `access_token` | localStorage `access_token` | cookie `cc-at` | **Yes** | Server-side; `useAccessToken` returns `''` on the client. The proxy injects the token into SCAPI requests. |
30+
| `expires_in` | localStorage `expires_in` | derived from cookie `cc-at-expires` | No | Cookie carries the absolute epoch (seconds) of the JWT `exp` claim. |
31+
| `refresh_token` (guest) | cookie `cc-nx-g` | cookie `cc-nx-g` | **Yes** (HttpOnly mode only) | Cookie name is the same in both modes; only the HttpOnly flag changes. |
32+
| `refresh_token` (registered) | cookie `cc-nx` | cookie `cc-nx` | **Yes** (HttpOnly mode only) | Same as above. |
33+
| `refresh_token_expires_in` | localStorage `refresh_token_expires_in` | cookie `cc-nx-expires` | No | Cookie carries the absolute epoch (seconds) when the refresh token expires; replaces the old `cc-nx-exists` marker. |
34+
| `customer_id` | localStorage `customer_id` | cookie `customer_id` | No | |
35+
| `customer_type` | localStorage `customer_type` | cookie `customer_type` | No | Derived from JWT `isb` claim (`guest` / `registered`). |
36+
| `enc_user_id` | localStorage `enc_user_id` | cookie `enc_user_id` | No | |
37+
| `usid` | cookie `usid` | cookie `usid` | No | Already a cookie in non-HttpOnly mode; lifetime aligns to the refresh-token TTL in HttpOnly mode. |
38+
| `id_token` | localStorage `id_token` | cookie `id_token` | No | Expiry tied to the access-token JWT `exp`. |
39+
| `idp_access_token` | localStorage `idp_access_token` | cookie `idp_access_token` | **Yes** | Unreadable from JavaScript in HttpOnly mode; getter returns `''`. |
40+
| `idp_refresh_token` | localStorage `idp_refresh_token` | cookie `idp_refresh_token` | **Yes** | Unreadable from JavaScript in HttpOnly mode; getter returns `''`. |
41+
| `uido` | localStorage `uido` | cookie `uido` | No | Identifies the IDP origin (e.g. `slas`, `ecom`). |
42+
| `dnt` | localStorage `dnt` (user preference) + cookie `dw_dnt` | localStorage `dnt` + cookies `dw_dnt`, `cc-at-dnt` | No | The user-preference value stays in localStorage in both modes. `cc-at-dnt` reflects the JWT `dnt` claim. |
43+
| `token_type` | localStorage `token_type` | localStorage `token_type` | n/a | Always `Bearer`; unused inside PWA Kit. |
44+
| `code_verifier` | localStorage `code_verifier` | localStorage `code_verifier` | n/a | PKCE intermediate; never migrated. |
45+
| `dwsid` (hybrid only) | cookie `dwsid` | cookie `dwsid` | Set by B2C Commerce | B2C Commerce owns this cookie; PWA Kit only reads it and forwards it as the `sfdc_dwsid` header to maintain server affinity. |
46+
47+
## Reading these in your code
48+
49+
`commerce-sdk-react`'s `Auth` class (and the `useAccessToken`, `useCustomerType`, `useCustomerId`, etc. hooks) handle the routing for you—the storage type is selected per-property, and `enableHttpOnlySessionCookies` switches the migrated keys to the cookie store automatically. Application code that goes through these hooks works unchanged in either mode.
50+
51+
If you read these values directly (for analytics, debugging, or hybrid SFRA pages), use the table above to pick the right source.
52+
53+
## See also
54+
55+
- [HttpOnly Session Cookies — Architecture](./httponly-cookies-architecture.md)—request-flow diagrams, proxy modes, configuration steps for enabling the feature.
56+
- [Hybrid Cookie Domain Configuration](./hybrid-cookie-domain-configuration.md)—how to share these cookies across a PWA Kit + SFRA deployment.
57+
- [`packages/commerce-sdk-react/src/auth/index.ts`](../packages/commerce-sdk-react/src/auth/index.ts)`DATA_MAP` and `HTTPONLY_COOKIE_BACKED_KEYS` are the source of truth for the table above.
58+
- [`packages/pwa-kit-runtime/src/ssr/server/httponly-cookie-config.js`](../packages/pwa-kit-runtime/src/ssr/server/httponly-cookie-config.js)—the canonical cookie-name and attribute table the SLAS proxy uses.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Hybrid Cookie Domain Configuration
2+
3+
> **WIP: HttpOnly session cookies are in-progress. Do not enable `enableHttpOnlySessionCookies` in production.**
4+
5+
Maintain shopper sessions across subdomains in hybrid PWA Kit and SFRA storefronts by configuring a shared parent cookie domain. Without this configuration, browsers isolate cookies to individual hosts, causing shoppers to appear logged out and lose their baskets when crossing subdomain boundaries. This guide shows how to align the cookie domain settings on both PWA Kit and B2C Commerce to enable seamless cross-subdomain navigation.
6+
7+
## Overview
8+
9+
A hybrid deployment runs PWA Kit and SFRA together, typically behind a single CDN that presents a unified top-level domain to shoppers. Even with that unified front, individual sites within the storefront may live on distinct subdomains—for example `siteA.example.com` and `siteB.example.com`, or a marketing host on `www.example.com` alongside a PWA-served catalog on `shop.example.com`.
10+
11+
Browsers default each `Set-Cookie` response to the **exact host** that issued it. So a cookie set by `siteA.example.com` is not sent on a request to `siteB.example.com` unless the issuer explicitly attaches `Domain=.example.com`. In a hybrid storefront where shoppers cross subdomains—or where PWA Kit and SFRA each serve a different subdomain of the same parent—that default scoping breaks the session: the shopper appears logged out, the basket is empty, hybrid auth fails.
12+
13+
The fix is to set `Domain=` on both sides to a shared parent domain (for example `.example.com`) so the same cookies are sent on every request under that domain. PWA Kit and SFRA configure this in different places—that's why this doc exists: to capture both sides of the configuration in one spot, plus a verification checklist for the seam between them.
14+
15+
For the full list of cookies affected, see [HttpOnly Mode: SLAS Property Storage Mapping](./httponly-storage-mapping.md). For the request-flow architecture, see [HttpOnly Session Cookies — Architecture](./httponly-cookies-architecture.md).
16+
17+
## 1. Configure the cookie domain on PWA Kit
18+
19+
Set `commerceAPI.cookieDomain` in your project's `config/default.js`:
20+
21+
```js
22+
module.exports = {
23+
app: {
24+
commerceAPI: {
25+
// ...
26+
cookieDomain: '.example.com'
27+
}
28+
}
29+
}
30+
```
31+
32+
This setting applies to all auth cookies set by PWA Kit—both the client-side cookies set by `commerce-sdk-react` and the server-side HttpOnly cookies set by the SLAS proxy when `enableHttpOnlySessionCookies` is on. See the [storage mapping reference](./httponly-storage-mapping.md) for the full cookie inventory.
33+
34+
When you first turn `cookieDomain` on for an existing site, both PWA Kit and the SLAS proxy emit an additional expiring `Set-Cookie` for any pre-existing host-scoped cookie of the same name. This prevents the browser from holding both a host-scoped and a domain-scoped cookie for the same key (which causes non-deterministic reads).
35+
36+
### Validation rules
37+
38+
`cookieDomain` must:
39+
40+
- be a plain hostname or a leading-dot domain (e.g. `.example.com`).
41+
- not contain wildcards or whitespace, and not contain any of `* , ; =`.
42+
43+
If the value contains any of those characters, both `commerce-sdk-react` and the SLAS proxy log a warning and fall back to host-scoped cookies. The browser would silently reject the value otherwise—the warning makes the misconfiguration visible.
44+
45+
The browser will **silently drop** a `Domain=` attribute that doesn't match the request hostname. For example, on the default `*.mobify-storefront.com` MRT host, a `Domain=.example.com` Set-Cookie header has no effect. You must serve PWA Kit from a custom domain that falls under `cookieDomain` (attach the domain to your MRT environment via the existing CNAME/Certificate flow).
46+
47+
## 2. Configure the cookie domain on B2C Commerce
48+
49+
The cookie domain in B2C Commerce for hybrid sessions is configured in Business Manager under:
50+
51+
**Merchant Tools → Site Preferences → Hybrid Auth Settings**
52+
53+
The cookie-domain setting accepts a numeric level that controls how broadly B2C Commerce scopes the `dwsid` (and other hybrid session) cookies. Only two values are valid:
54+
55+
| Value | Resulting scope | Example | When to use |
56+
| --- | --- | --- | --- |
57+
| `0` (default) | Host name only | `www.example.com` cookies are not sent on `api.example.com` | Single-host deployments where PWA Kit and SFRA share the exact same hostname. |
58+
| `2` | First-level (parent) domain | `.example.com`—sent on `www.example.com`, `api.example.com`, `pwa.example.com`, etc. | Hybrid deployments where PWA Kit and SFRA live on different subdomains of the same parent domain. **Use this value to match `commerceAPI.cookieDomain: '.example.com'` on the PWA Kit side.** |
59+
60+
> **Do not set the value to `1`.** Level `1` would scope the cookie to the top-level domain (e.g. `.com`) and is rejected for security reasons—a cookie scoped that broadly would leak across unrelated sites. No values other than `0` and `2` are accepted.
61+
62+
The **HttpOnly True** toggle on the same Hybrid Auth Settings page should be left at its default (disabled) value. For broader Hybrid Auth setup see <!-- TODO: link to Hybrid Auth documentation --> [Hybrid Auth documentation (TBD)]().
63+
64+
### Matching the two sides
65+
66+
For PWA Kit and SFRA to share a session, the two settings must agree.
67+
68+
#### ✓ Valid configurations
69+
70+
| PWA Kit `commerceAPI.cookieDomain` | B2C Commerce Hybrid Auth cookie domain | When to use |
71+
| --- | --- | --- |
72+
| unset (or matches the host exactly) | `0` | Single-host hybrid: PWA Kit and SFRA both serve from the exact same hostname. |
73+
| `.example.com` | `2` | Cross-subdomain hybrid: any subdomain of `example.com` (e.g. `siteA.example.com`, `siteB.example.com`) can share the session. |
74+
75+
#### ✗ Invalid configurations
76+
77+
These combinations leave one side host-scoped and the other domain-scoped, so cross-subdomain navigation breaks:
78+
79+
| PWA Kit `commerceAPI.cookieDomain` | B2C Commerce Hybrid Auth cookie domain | What goes wrong |
80+
| --- | --- | --- |
81+
| `.example.com` | `0` | `dwsid` is host-scoped while PWA Kit cookies are domain-scoped. SFRA's session cookie doesn't follow the shopper across subdomains. |
82+
| unset | `2` | `dwsid` is domain-scoped while PWA Kit cookies are host-scoped. PWA Kit's auth cookies don't follow the shopper across subdomains. |
83+
84+
If your site is configured by an SI or by Salesforce, file a request with the team that manages your Business Manager.
85+
86+
## 3. Verification checklist
87+
88+
Run through this list once both PWA Kit and B2C Commerce are configured. You can do most of this in Chrome DevTools → Application → Cookies.
89+
90+
- [ ] **Domain attributes match.** Log into the storefront and confirm that `dwsid` and `cc-nx-g_{siteId}` (or `cc-nx_{siteId}` for registered shoppers) both show the **same** Domain—for example `.example.com`. A mismatch means one side is still host-scoped.
91+
- [ ] **All HttpOnly cookies carry the configured Domain.** Inspect every cookie listed in the storage-mapping table and confirm Domain matches `cookieDomain`. The HttpOnly cookies (`cc-at`, `cc-nx-g`, `cc-nx`, `idp_access_token`, `idp_refresh_token`) should also show the HttpOnly attribute set.
92+
- [ ] **No duplicate host-scoped cookies remain.** After the first login post-migration, there should be **one** entry per cookie name. If you see two entries (one host-scoped, one with `Domain=`), the migration cleanup didn't run—check that you're running a build that includes server-side `cookieDomain` support, and that the user logged in *after* enabling `cookieDomain` (existing sessions are cleaned up on the next set, not retroactively).
93+
- [ ] **Cross-host session works.** Log in on the PWA Kit host, navigate to an SFRA-served URL on a sibling host (or vice-versa), and confirm the shopper stays logged in (no extra `/oauth2/token` round trip in the network tab, basket persists).
94+
- [ ] **`sfdc_dwsid` is forwarded.** In the SCAPI proxy request (`/mobify/proxy/api/...`), confirm an `sfdc_dwsid` header carries the same value as the `dwsid` cookie. If it's missing, the cookie isn't reaching the proxy—almost always a Domain mismatch.
95+
- [ ] **Logout clears domain-scoped cookies.** Hit logout, then inspect cookies—every cookie in the storage-mapping table should be gone (or have `Max-Age=0` / past `Expires`). If some remain, those were set on a domain that didn't match `cookieDomain` at logout time.
96+
- [ ] **Invalid-domain warning is absent.** Search server logs for `Invalid cookieDomain`. If present, the regex rejected your value and cookies are falling back to host-scoped—fix the config.
97+
- [ ] **Production hostname resolves to `cookieDomain`.** The custom domain attached to your MRT environment must be a subdomain of `cookieDomain` (e.g. `pwa.example.com` under `.example.com`). On the default `*.mobify-storefront.com` host, the Domain attribute is silently dropped.
98+
99+
## See also
100+
101+
- <!-- TODO: link to Hybrid Auth documentation --> [Hybrid Auth documentation (TBD)]()—overview of the hybrid PWA Kit + SFRA auth model. The reciprocal link from that doc back to this one is the dual-linkage referenced when both docs are published.
102+
- [HttpOnly Mode: SLAS Property Storage Mapping](./httponly-storage-mapping.md)—the canonical list of cookies affected by `cookieDomain`.
103+
- [HttpOnly Session Cookies — Architecture](./httponly-cookies-architecture.md)—request-flow diagrams and the broader configuration guide.
104+
- [`packages/commerce-sdk-react/src/auth/storage/cookie.ts`](../packages/commerce-sdk-react/src/auth/storage/cookie.ts)—client-side cookie store that consumes `cookieDomain` and runs the host-vs-domain cleanup pattern.
105+
- [`packages/pwa-kit-runtime/src/ssr/server/process-token-response.js`](../packages/pwa-kit-runtime/src/ssr/server/process-token-response.js)—server-side proxy that mirrors the same logic for HttpOnly cookies.

0 commit comments

Comments
 (0)