Subchapter 9.19
references/inline-recipes/how-to-code-members-non-astro.mdMarkdown12 KBView on GitHub
RECIPE: How to Code Member Auth on a non-Astro Frontend (manual OAuthStrategy, @wix/members)
A concise contract for wiring login / sign-up / logout and member-gated surfaces into a non-Astro frontend — a Vite/React/Vue SPA or plain static HTML, whether managed-non-Astro or self-managed. This is the how (which calls, which preconditions, which failure modes), not the what — which pages are gated, what the account page shows, and the design come from the request you’re fulfilling.
⚠️ AXIS GUARD — this recipe is for non-Astro only. Here you drive the OAuth handshake yourself on the same manual client
non-astro.mdbuilds for visitors. If the frontend is Astro, stop and readhow-to-code-members-astro.md— Astro ships built-in/api/auth/loginroutes and auto-auth, and building theOAuthStrategyhandshake below on Astro is wrong (and 500s at SSR). Don’t cross the streams.
One mechanism, not three. Sign-up, log-in and log-out are the same flow — the Wix login page logs in an existing member or registers a new one in one step; you never build a separate “sign up” call. Member tokens are the same shape as visitor tokens, just
role: member: login just swaps the token set on the client you already have.
Pinned docs (read before wiring — curl the .md directly):
OAuthStrategy reference (member flows, token/session methods): https://dev.wix.com/docs/sdk/core-modules/sdk/oauth-strategy.md (opens in a new tab)?apiView=SDK)clientId only, never the secret. Member auth uses the same public OAuth client id as the visitor client (non-astro.md N6). No client_secret in the frontend, ever.getAuthUrl serves the Wix login page; against an unpublished site it fails the redirect with a non-obvious error. Publish before testing login (like the events payment-method precondition).redirectUri must be in the OAuth app’s allowed redirect URIs — exact match, and YOU must register it (it is not automatic). A mismatch (or a missing entry) is the classic login 4xx — login is dead until it’s registered. wix release auto-registers the deployed origin (allowedRedirectDomains), but not this member-login callback (allowedRedirectUris) — that’s a separate field and a separate, manual step. Because the callback embeds the deployed origin, it’s a post-deploy step: register it right after release. ⚠️ Do not conclude allowedRedirectUris is read-only/dashboard-only — the UpdateOAuthApp doc may not list it prominently, but a masked PATCH sets it (the field mask is required or it silently no-ops). Mechanics: managed/DEPLOYMENT.md → “Member login on a non-Astro frontend”. Register both the exact callback (window.location.origin + '/callback') and the https://*-<host>/callback wildcard; don’t rely on trailing-slash/path variants matching.how-to-code-members-custom-login.md — that path builds the form and drives client.auth.register/login directly (works on any project type). Use externally-managed only when specifically requested. When no custom form or extra fields are named, the Wix login page below is the default — don’t build a custom form unprompted.loggedIn()-based gating below runs on this layer alone.SETUP.md). @wix/members getCurrentMember() returns data only once that app is present.If getCurrentMember() returns empty/errors on a site where loggedIn() is true, suspect the Members Area app isn’t installed — not a code bug.
Reuse the one visitor client non-astro.md builds (createClient({ modules, auth: OAuthStrategy({ clientId }) })) — don’t make a second client. Login is three steps across a redirect.
// 1 · start login (sign-up is the SAME call) — from a "Log in" button
const redirectUri = window.location.origin + '/callback'; // MUST be allow-listed, exact match
const originalUri = window.location.href; // where to send the member back after
const oAuthData = client.auth.generateOAuthData(redirectUri, originalUri); // PKCE: state + codeVerifier
localStorage.setItem('wixOAuthData', JSON.stringify(oAuthData)); // survive the redirect
const { authUrl } = await client.auth.getAuthUrl(oAuthData);
window.location.href = authUrl; // → Wix login page (login or register)
// 2 · on the callback page (/callback)
const oAuthData = JSON.parse(localStorage.getItem('wixOAuthData'));
const returned = client.auth.parseFromUrl(); // { code, state } or { error }
if (returned.error) { /* surface returned.errorDescription; do NOT loop back into getAuthUrl */ }
const tokens = await client.auth.getMemberTokens(returned.code, returned.state, oAuthData);
client.auth.setTokens(tokens); // subsequent SDK calls now run as the member
persistTokens(tokens); // see persistence below
window.location.href = returned.originalUri ?? '/'; // back to where login started
// 3 · logout
const { logoutUrl } = await client.auth.logout(window.location.href);
clearPersistedTokens();
window.location.href = logoutUrl;Check auth state anywhere: client.auth.loggedIn() — false = anonymous visitor, true = logged-in member. Gate a view on it (render the account UI only when true; otherwise show a “Log in” control that runs step 1).
Member tokens are the same token set the visitor session machinery already handles; login just swaps them in.
setTokens so a reload stays logged in. On app boot, if you have a stored set, client.auth.setTokens(stored) before first render.client.auth.renewToken(refreshToken) (or the SDK’s automatic renewal when you re-hydrate via setTokens) — same as the visitor refresh flow. A member session expiring is a normal state; refresh, don’t force a re-login unless the refresh token is gone.import { members } from '@wix/members';
// with the member tokens set on the client:
const { member } = await client.members.getCurrentMember({ fieldsets: ['FULL'] });
// member.profile?.nickname, member.profile?.photo, member.loginEmail, member.contactId, member.rolesgetCurrentMember, NOT getMyMember. The REST method is named Get My Member and the SDK docs page may show GetMyMember, but @wix/members exports it as client.members.getCurrentMember — calling getMyMember(...) throws is not a function at runtime. It’s a silent trap: a logged-out smoke test never reaches the call, so it only fails once a real member loads the account page.@wix/members (getCurrentMember / getMember / updateMember) — visitor/member auth, production-ready. Do NOT use @wix/site-members (wixSiteMembers, “Current Member”) — it’s in Developer Preview (“not for production”). Only if a profile-privacy toggle is specifically requested.wix:image:// identifier — resolve with media.getScaledToFillImageUrl (non-astro.md N7); never hand-build the CDN URL.auth.elevate() is the permission axis (→ app/admin scope). A member reading their own data (own orders/bookings/subscriptions, plan-gated content, their profile) is authorized under the member token, no elevation — reaching for elevate to “make member data work” is the wrong axis and doesn’t help.auth.elevate at all (non-astro.md N5). Admin/site-wide reads (everyone’s orders, all members) genuinely can’t run on this path — that’s a real limitation, but member features don’t need it, so it does not block login / account / gated content. If a feature truly needs an admin read, it needs a server (out of scope for a static SPA).If the site has pricing-plans, login is required, not optional. Ordering a plan (orders.createOnlineOrder(planId)) orders it for a logged-in member; anonymous → the Wix flow forces sign-up. orders.memberListOrders() and “my subscription” reads return nothing for an anonymous visitor. So the plans grid is public, but the subscribe button and the my-subscription surface both need the login flow above; a logged-in member calling orders.createOnlineOrder needs no onBehalf. Everywhere else (stores “my orders”, bookings “my bookings”, events “my registrations”), login is a soft add-on — the purchase/RSVP/book action runs fine as an anonymous visitor; only the account view of it needs a member.
Correct member auth on a non-Astro frontend:
OAuthStrategy client and drives the handshake — generateOAuthData → getAuthUrl → parseFromUrl → getMemberTokens → setTokens, logout, loggedIn() — never an Astro /api/auth/* route;clientId only, site published, redirectUri allow-listed exact-match, default to the Wix login page;renewToken), re-hydrating with setTokens on boot and clearing them on logout;@wix/members getCurrentMember (not the dev-preview @wix/site-members), resolving the photo wix:image:// URI, expecting PUBLIC-only fields for other members;auth.elevate (wrong axis for own-data reads; unavailable on a serverless SPA anyway);