Skip to main content

Authentication & accounts

Three composables cover identity and account data: useAuth (login, registration, password reset), useCompany (B2B company switching) and useAddress (address CRUD). This page explains their API calls and the auth-token lifecycle — the part most worth getting right.

Signatures: useAuth, useCompany, useAddress.

The auth-token lifecycle

This is the single most important thing to understand.

useAuth().login() calls services.login.login(), receives a session with an accessToken, and then:

// inside useAuth — simplified
const cfg = graphqlClient.getConfig();
graphqlClient.updateConfig({
headers: { ...cfg.headers, Authorization: `Bearer ${accessToken}` },
});
onAuthHeaderUpdate?.(accessToken);
  • The Bearer header is set in memory only, on the GraphQLClient, for the rest of the page session. From that point every composable's calls are authenticated, because they all share that client.
  • It deliberately does not call graphqlClient.setAccessToken() — the SDK's default resolver for that persists the JWT to localStorage['access_token'], which is an XSS exposure. The package will not do that for you.
  • Cross-reload auth is your responsibility. The recommended pattern: your onAuthHeaderUpdate callback POSTs the token to your own backend, which sets an httpOnly cookie; your GraphQL proxy route then injects the Bearer header server-side on every request. The token never touches client-readable storage.

So: useAuth authenticates the current page session in memory and hands you the token; persisting it safely across reloads is wired in your app.

useAuth

const { login, registerContact, registerCustomer, forgotPassword,
loading, error } = useAuth({ graphqlClient, onAuthHeaderUpdate });
ActionSDK callsNotes
login(email, password)login.loginuser.getViewerSets the in-memory Bearer header; returns the user
registerContact(input)company.createCompanyuser.registerContactlogin.loginaddress.createCompanyAddressB2B contact — see ordering below
registerCustomer(input)user.registerCustomerlogin.loginaddress.createCustomerAddressB2C customer
forgotPassword(email)user.sendPasswordResetEmailFire-and-forget reset email

All four resolve to a result object (LoginResult or { success, error? }) — none throw.

Registration ordering matters

registerContact and registerCustomer log in before creating addresses. createCompanyAddress / createCustomerAddress are account-scoped mutations the backend authorises against the logged-in user — calling them anonymously (API key only) returns FORBIDDEN. So the flow is: create the account → login() (sets the Bearer header) → create addresses with that authenticated session.

If autoLogin is false, the implicit login is still performed for the address step, then dropped afterwards (graphqlClient.clearAccessToken() and the in-memory Authorization header is stripped) so the caller does not observe a logged-in state it did not ask for.

getViewer and tracked attributes

After login, user.getViewer fetches the full Contact/Customer. The configuration option lets you request extra tracked attributes (contactTrackAttributes, companyTrackAttributes, …) and a contact PA-config sub-selection — these become part of the ViewerInput sent to the backend.

useCompany

const company = useCompany({ graphqlClient, user, companyId });

For B2B contacts that belong to multiple companies. It calls services.company to list the contact's companies and switch the active one. graphqlClient is typed GraphQLClient | null | undefined so the hook can be called unconditionally (Rules of Hooks) before the client is ready — its methods return a { success: false, error } result until a client is present.

Switching the active company changes the companyId used for cart lookup, pricing and authorization downstream — feed the result back into your PropellerProvider value so the whole tree re-resolves.

useAddress

const address = useAddress({ graphqlClient, user, companyId });

CRUD over the user's saved addresses, via services.address (createAddress, updateAddress, deleteAddress). Like useCompany, it accepts a nullable graphqlClient and its mutations bail with a result object — never a throw — when the client or user is absent.

Address mutations are account-scoped: the user must be authenticated (the in-memory Bearer header from useAuth().login() must be set), or the backend rejects them.