Skip to main content

sfnext-authentication

Work with Storefront Next's server-only authentication and session architecture (SLAS guest/registered tokens, httpOnly cookies, auth.server.ts middleware). Use for getAuth, useAuth, updateAuth, destroyAuth, protecting account routes, login/logout actions, guest-to-registered basket/wishlist merge, "session expired" redirects, 401 recovery loops, cc-at/cc-nx/usid/dwsid cookies, cookie names or expiry, passwordless login, social login, OTP email verification, passkeys, guest order lookup, or refresh-token lifetime settings. Do not use for Turnstile, CSP headers or cookie-domain config (use `storefront-next:sfnext-security`), calling SCAPI clients (use `storefront-next:sfnext-scapi`), or sharing sessions with SFRA (use `storefront-next:sfnext-hybrid-storefronts`).

معلومات المصدر

المستودع
SalesforceCommerceCloud/b2c-developer-tooling
آخر نشاط في المصدر
٤ أكتوبر ٢٠٢٦ في ١٦:٤٠
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٥٦
التفرعات
٢٢

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
4 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
sfnext-authentication
description
Work with Storefront Next's server-only authentication and session architecture (SLAS guest/registered tokens, httpOnly cookies, auth.server.ts middleware). Use for getAuth, useAuth, updateAuth, destroyAuth, protecting account routes, login/logout actions, guest-to-registered basket/wishlist merge, "session expired" redirects, 401 recovery loops, cc-at/cc-nx/usid/dwsid cookies, cookie names or expiry, passwordless login, social login, OTP email verification, passkeys, guest order lookup, or refresh-token lifetime settings. Do not use for Turnstile, CSP headers or cookie-domain config (use `storefront-next:sfnext-security`), calling SCAPI clients (use `storefront-next:sfnext-scapi`), or sharing sessions with SFRA (use `storefront-next:sfnext-hybrid-storefronts`).
# Storefront Next Authentication Authentication is **server-only**. One middleware (`src/middlewares/auth.server.ts`, registered in the `middleware` array of `src/root.tsx`) runs on every request: it reads cookies, validates or refreshes the SLAS access token, performs a guest login when nothing usable exists, and writes `Set-Cookie`. Tokens never reach the browser. Components only see a small non-sensitive slice through `useAuth()`. Read `docs/README-AUTH.md` in your project for the full reference; this skill is the task-oriented summary. Route modules use server `loader`/`action` only, never `clientLoader`/`clientAction`. ## Request flow 1. Valid access token (JWT `exp` in the future): use it. 2. Expired access token with a refresh token: refresh, rewrite `cc-at`. 3. No tokens: guest login. 4. Registered shopper whose refresh fails: 307 to `/login?returnUrl=<path>&error=session_expired` (site/locale prefix included) and a fresh guest session is created in parallel. Guests are silently re-issued a guest session. 5. A SCAPI call returning 401 throws `AuthTokenInvalidError`; the middleware clears state, re-runs login/refresh and 307-redirects to the same URL. The short-lived `cc-auth-recover` cookie (30 s) prevents loops. User type comes from the access-token JWT (registered tokens carry an `rcid` claim), not from which cookie exists. `customerId` is **not** a cookie; it is derived per request from the JWT. ## Read auth on the server ```typescript import { getAuth } from '@/middlewares/auth.server'; import type { LoaderFunctionArgs } from 'react-router'; export async function loader({ context }: LoaderFunctionArgs) { const auth = getAuth(context); // full SessionData, server only const isRegistered = auth.userType === 'registered'; return { isRegistered }; // never return auth.accessToken / refreshToken } ``` ## Read auth in components ```tsx import { useAuth } from '@/providers/auth'; function AccountBadge() { const auth = useAuth(); // PublicSessionData | undefined return auth?.userType === 'registered' ? <span>Welcome back</span> : <SignInLink />; } ``` `PublicSessionData` is exactly `customerId`, `userType`, `usid`, `encUserId`, `trackingConsent`. The root loader derives it with `getPublicSessionData` (from `@/middlewares/auth.utils`) and passes it to `AuthProvider`; there is no client `getAuth`. ## Protect a route Branch inside the server loader. Prefer the existing account layout (`src/routes/_app.account.tsx`) for anything under `/account` instead of adding per-route checks. ```typescript import { redirect } from 'react-router'; import { getAuth } from '@/middlewares/auth.server'; import { buildUrlFromContext } from '@/lib/url.server'; import { routes } from '@/route-paths'; export async function loader({ context }: LoaderFunctionArgs) { if (getAuth(context).userType !== 'registered') { throw redirect(buildUrlFromContext(routes.login, context)); // keeps site/locale prefix } // ... } ``` ## Log in and out Never write auth cookies yourself. Use the helpers exported from `@/middlewares/auth.server`: ```typescript import { loginRegisteredUser, updateAuth } from '@/middlewares/auth.server'; export async function action({ request, context }: ActionFunctionArgs) { const form = await request.formData(); const tokens = await loginRegisteredUser(context, String(form.get('email')), String(form.get('password'))); updateAuth(context, tokens); // re-derives userType/customerId/usid, swaps cc-nx-g for cc-nx return redirect('/account'); } ``` `updateAuth` accepts a SLAS token response or an updater function (`updateAuth(context, (cur) => ({ ...cur, codeVerifier }))`). Logout: `destroyAuth(context)` clears every auth cookie on the response; the shipped `src/routes/_empty.logout.ts` also calls `clients.auth.logout` and `destroyBasket`. Prefer `src/lib/api/auth/standard-login.server.ts` and `social-login.server.ts` over hand-rolled flows: they already handle the merge below. Other exported helpers: `loginGuestUser`, `refreshAccessToken`, `authorizePasswordless`, `getPasswordLessAccessToken`, `getPasswordResetToken`, `resetPasswordWithToken`, `requestOtp`, `verifyOtp`, `flashAuth(context, message)` (clear session plus an error message), `clearInvalidSessionAndRestoreGuest(context)` (deleted customer or corrupted session). ## Guest to registered merge `customerId` changes from the guest `gcid` to the registered `rcid` at the token swap, and SCAPI rejects the guest id under the registered token. So: snapshot guest-owned data (wishlist) **before** the swap, then `mergeBasket` and `mergeWishlist` **after**. `src/lib/api/auth/social-login.server.ts` is the reference implementation. `dw_dnt` tracking consent is preserved across the swap. ## Common SCAPI calls for account data These are real operations: `clients.shopperCustomers.getCustomerOrders`, `getCustomerProductLists` (wishlists), `getCustomer`. Use the existing helpers (`src/lib/api/order.server.ts`, `wishlist.server.ts`, `customer.server.ts`) rather than calling clients directly. Client setup and the `@/lib/api-clients.server` import are covered in `storefront-next:sfnext-scapi`. ## Configuration | Setting | Where | |---|---| | Refresh-token lifetime | `PUBLIC_COMMERCE_API_GUEST_REFRESH_TOKEN_EXPIRY_SECONDS` (max 30 days), `PUBLIC_COMMERCE_API_REGISTERED_REFRESH_TOKEN_EXPIRY_SECONDS` (max 90 days) | | Feature flags | `features.passwordlessLogin`, `features.passkey`, `features.socialLogin`, `features.otpRequest`, `auth.otpLength` in `config.server.ts` | | Private SLAS client | `commerce.api.privateKeyEnabled` (required for passwordless / OTP) | | Cookie domain | `app.cookies.domain` (see `storefront-next:sfnext-security`) | `secure` on cookies is only set on deployed (HTTPS) environments; local `pnpm dev` over `http://localhost` writes non-secure cookies so Safari keeps them. ## Pitfalls - Reading tokens with `document.cookie`: impossible by design (httpOnly). Use `useAuth()` or loader data. - Returning `getAuth(context)` from a loader leaks tokens into the page. Return only derived values. - Refreshing tokens manually in routes: the middleware already does it. - Logging `accessToken` or `refreshToken`: never. - Changing the cookie domain on a live site leaves old host-only cookies shadowing new ones; roll out in a quiet window. - A loop of redirects to `/login?error=session_expired` usually means refresh tokens are being rejected (SLAS client config, or cookie domain mismatch between hosts). ## References - [COOKIES.md](references/COOKIES.md): full cookie inventory, namespacing, expiry, hints. - [LOGIN-FLOWS.md](references/LOGIN-FLOWS.md): passwordless, social, OTP, passkeys, password reset, guest order lookup, email cartridge prerequisites. - In your project: `docs/README-AUTH.md`, `docs/README-EMAIL-VERIFICATION.md`, `docs/README-GUEST-ORDER-LOOKUP.md`, `docs/README-EMAIL-CARTRIDGE.md`. ## Related Skills - `storefront-next:sfnext-security` - Turnstile bot protection, CSP for social providers, cookie domain - `storefront-next:sfnext-scapi` - SCAPI clients and custom APIs - `storefront-next:sfnext-hybrid-storefronts` - shared `dwsid` session bridge with SFRA - `storefront-next:sfnext-analytics-consent` - `dw_dnt` tracking consent - `storefront-next:sfnext-configuration` - `config.server.ts` and `PUBLIC__` overrides - `b2c-cli:b2c-slas` - manage SLAS clients and scopes
عرض على GitHub