Shopper sign-in
Shoppers sign in on the shop's own page, with OAuth (authorization code with S256 PKCE). Your site gets their tokens and never sees their password.
- In the panel, add your site's return address to a publishable key's Sign-in return addresses. The key's client id is shown below it.
- Make a PKCE pair and a state, keep them, and send the shopper to
authorizeUrl. - On your return address, check the state and trade the code with
exchangeCode. - Act as the shopper with
withAccessToken, and renew the tokens withrefresh.
Access tokens last an hour and refresh tokens 30 days.
Reach these through storefront.signIn. The PKCE pair and state come from the PKCE helpers.
Build the sign-in address
storefront.signIn.authorizeUrl(params: {
clientId: string;
redirectUri: string;
state: string;
codeChallenge: string;
}): string
The shop's sign-in page to send the shopper to: https://{host}/account/authorize with these query values:
| Query value | Value |
|---|---|
client_id |
The client id you pass. |
redirect_uri |
The return address you pass. |
response_type |
code |
scope |
storefront:customer |
state |
The state you pass. |
code_challenge |
The PKCE challenge you pass. |
code_challenge_method |
S256 |
Parameters
| Parameter | Type | |
|---|---|---|
params.clientId |
string |
The publishable key's client id. |
params.redirectUri |
string |
One of the key's sign-in return addresses. |
params.state |
string |
A random value you keep, to check on the return address. |
params.codeChallenge |
string |
The challenge of the PKCE pair whose verifier you keep. |
Returns
The address, as a string. No request is made.
Example
import { createPkcePair, createState } from '@storehugs/integration';
const { verifier, challenge } = await createPkcePair();
const state = createState();
sessionStorage.setItem(
'storehugs_sign_in',
JSON.stringify({ verifier, state }),
);
window.location.assign(
storefront.signIn.authorizeUrl({
clientId: 'the-key-client-id',
redirectUri: 'https://www.example.com/account/callback',
state,
codeChallenge: challenge,
}),
);
Exchange the code
storefront.signIn.exchangeCode(params: {
code: string;
clientId: string;
redirectUri: string;
codeVerifier: string;
}): Promise<TokenSet>
Trades the code from the redirect for the shopper's tokens, with the authorization_code grant. No Idempotency-Key is sent.
Parameters
| Parameter | Type | |
|---|---|---|
params.code |
string |
The code query value on your return address. |
params.clientId |
string |
The publishable key's client id. |
params.redirectUri |
string |
The same return address you sent the shopper with. |
params.codeVerifier |
string |
The verifier of the PKCE pair you kept. |
Returns
A TokenSet:
| Field | Type | Description |
|---|---|---|
token_type | string | One of Bearer. |
expires_in | integer | Seconds (3600). |
access_token | string | |
refresh_token | string |
Errors
| Status | When |
|---|---|
400 | An OAuth error (invalid_request, invalid_grant, unsupported_grant_type…). |
401 | invalid_client: not the client id of one of this shop's active publishable keys. |
402 | The shop is suspended (message only). |
403 | The shop is not live. |
404 | Not found, or the shop's plan has no Integration API. |
429 | Rate limited. |
Example
const params = new URLSearchParams(window.location.search);
const saved = JSON.parse(sessionStorage.getItem('storehugs_sign_in')!);
if (params.get('state') !== saved.state) {
throw new Error('Sign-in state does not match');
}
const tokens = await storefront.signIn.exchangeCode({
code: params.get('code')!,
clientId: 'the-key-client-id',
redirectUri: 'https://www.example.com/account/callback',
codeVerifier: saved.verifier,
});
Refresh the tokens
storefront.signIn.refresh(params: { refreshToken: string; clientId: string }): Promise<TokenSet>
New tokens for a refresh token, with the refresh_token grant, before or after the access token runs out.
Parameters
| Parameter | Type | |
|---|---|---|
params.refreshToken |
string |
The shopper's refresh token. |
params.clientId |
string |
The publishable key's client id. |
Returns
A TokenSet:
| Field | Type | Description |
|---|---|---|
token_type | string | One of Bearer. |
expires_in | integer | Seconds (3600). |
access_token | string | |
refresh_token | string |
Errors
| Status | When |
|---|---|
400 | An OAuth error (invalid_request, invalid_grant, unsupported_grant_type…). |
401 | invalid_client: not the client id of one of this shop's active publishable keys. |
402 | The shop is suspended (message only). |
403 | The shop is not live. |
404 | Not found, or the shop's plan has no Integration API. |
429 | Rate limited. |
Example
const fresh = await storefront.signIn.refresh({
refreshToken: tokens.refresh_token,
clientId: 'the-key-client-id',
});
Act as the shopper
storefront.withAccessToken(accessToken: string): StorefrontClient
The same client, acting for a signed-in shopper: it sends the access token with every request, next to the key. The original client is unchanged.
Parameters
| Parameter | Type | |
|---|---|---|
accessToken |
string |
The shopper's access token. |
Returns
A new StorefrontClient. No request is made.
Example
const shopper = storefront.withAccessToken(tokens.access_token);
const me = await shopper.customer.get();