Loading...
Loading...
Implement advanced SLAS authentication patterns in B2C Commerce. Use when implementing passwordless login (email OTP, SMS OTP, passkeys), session bridging between PWA and SFRA, hybrid authentication, token refresh, or trusted system authentication. Covers authentication flows, token management, and JWT validation.
npx skill4agent add salesforcecommercecloud/b2c-developer-tooling b2c-slas-auth-patterns| Method | Use Case | User Experience |
|---|---|---|
| Password | Traditional login | Username + password form |
| Email OTP | Passwordless email | Code sent to email |
| SMS OTP | Passwordless SMS | Code sent to phone |
| Passkeys | FIDO2/WebAuthn | Biometric or device PIN |
| Session Bridge | Hybrid storefronts | Seamless PWA ↔ SFRA |
| Hybrid Auth | B2C 25.3+ | Built-in platform auth sync |
| TSOB | System integration | Backend service calls |
/oauth2/passwordless/loginpwdless_login_token// POST /shopper/auth/v1/organizations/{org}/oauth2/passwordless/login
async function initiatePasswordlessLogin(email, siteId) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/passwordless/login`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
user_id: email,
mode: 'callback',
channel_id: siteId,
callback_uri: 'https://yoursite.com/api/passwordless/callback'
})
}
);
// SLAS will POST to your callback_uri with pwdless_login_token
return response.json();
}pwdless_login_token// Your callback endpoint (receives POST from SLAS)
app.post('/api/passwordless/callback', async (req, res) => {
const { pwdless_login_token, user_id } = req.body;
// Generate 6-digit OTP
const otp = Math.floor(100000 + Math.random() * 900000).toString();
// Store token + OTP mapping (e.g., Redis with 10 min TTL)
await redis.setex(`pwdless:${otp}`, 600, JSON.stringify({
token: pwdless_login_token,
email: user_id
}));
// Send OTP via email (configure in SLAS Admin UI)
await sendOTPEmail(user_id, otp);
res.status(200).send('OK');
});// POST /shopper/auth/v1/organizations/{org}/oauth2/passwordless/token
async function exchangeOTPForToken(otp, clientId, clientSecret, siteId) {
// Retrieve stored token
const stored = JSON.parse(await redis.get(`pwdless:${otp}`));
if (!stored) throw new Error('Invalid or expired OTP');
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/passwordless/token`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${btoa(clientId + ':' + clientSecret)}`
},
body: new URLSearchParams({
grant_type: 'client_credentials',
hint: 'pwdless_login',
pwdless_login_token: stored.token,
channel_id: siteId
})
}
);
// Returns: { access_token, refresh_token, ... }
return response.json();
}// In your callback handler
const twilio = require('twilio')(accountSid, authToken);
async function sendOTPSMS(phoneNumber, otp) {
await twilio.messages.create({
body: `Your login code is: ${otp}`,
from: '+1234567890',
to: phoneNumber
});
}// Step 1: Verify identity via OTP first
// Use the Email OTP flow above to verify the user
// Step 2: Start passkey registration (requires valid access token)
async function startPasskeyRegistration(accessToken) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/webauthn/register/start`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: siteId
})
}
);
return response.json();
}
// Step 3: Create credential using WebAuthn API
async function createPasskey(options) {
const credential = await navigator.credentials.create({
publicKey: {
challenge: base64ToBuffer(options.challenge),
rp: { name: options.rp_name, id: options.rp_id },
user: {
id: base64ToBuffer(options.user_id),
name: options.user_name,
displayName: options.user_display_name
},
pubKeyCredParams: options.pub_key_cred_params,
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'required'
}
}
});
return credential;
}
// Step 4: Complete registration with SLAS
async function finishPasskeyRegistration(accessToken, credential) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/webauthn/register/finish`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: siteId,
credential_id: bufferToBase64(credential.rawId),
client_data_json: bufferToBase64(credential.response.clientDataJSON),
attestation_object: bufferToBase64(credential.response.attestationObject)
})
}
);
return response.json();
}// Step 1: Get authentication options
async function startPasskeyAuth() {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/webauthn/authenticate/start`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel_id: siteId })
}
);
return response.json();
}
// Step 2: Get credential using WebAuthn API
async function authenticateWithPasskey(options) {
const assertion = await navigator.credentials.get({
publicKey: {
challenge: base64ToBuffer(options.challenge),
rpId: options.rp_id,
allowCredentials: options.allow_credentials.map(c => ({
type: 'public-key',
id: base64ToBuffer(c.id)
})),
userVerification: 'required'
}
});
return assertion;
}
// Step 3: Complete authentication and get tokens
async function finishPasskeyAuth(assertion) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/webauthn/authenticate/finish`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
credential_id: bufferToBase64(assertion.rawId),
client_data_json: bufferToBase64(assertion.response.clientDataJSON),
authenticator_data: bufferToBase64(assertion.response.authenticatorData),
signature: bufferToBase64(assertion.response.signature),
channel_id: siteId
})
}
);
return response.json();
}dwsgstSession.generateGuestSessionSignature()dwsrstSession.generateRegisteredSessionSignature()// In PWA Kit: Get session bridge tokens
async function getSessionBridgeTokens(accessToken) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/session-bridge/token`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
channel_id: siteId,
login_id: customerId
})
}
);
// Returns: { dwsgst, dwsrst }
return response.json();
}
// Redirect to SFRA with tokens
function redirectToSFRA(dwsgst, dwsrst) {
const sfraUrl = new URL('https://sfra.yoursite.com/');
sfraUrl.searchParams.set('dwsgst', dwsgst);
if (dwsrst) {
sfraUrl.searchParams.set('dwsrst', dwsrst);
}
window.location.href = sfraUrl.toString();
}// In SFRA controller: Generate bridge tokens
var Session = require('dw/system/Session');
function bridgeToPWA() {
var dwsgst = Session.generateGuestSessionSignature();
var dwsrst = customer.authenticated ?
Session.generateRegisteredSessionSignature() : null;
response.redirect(
'https://pwa.yoursite.com/callback' +
'?dwsgst=' + dwsgst +
(dwsrst ? '&dwsrst=' + dwsrst : '')
);
}// In PWA Kit: Handle bridge callback
async function handleBridgeCallback(searchParams) {
const dwsgst = searchParams.get('dwsgst');
const dwsrst = searchParams.get('dwsrst');
// Exchange bridge tokens for access token
// Use hint=sb-guest for guest, hint=sb-user for registered
const hint = dwsrst ? 'sb-user' : 'sb-guest';
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/session-bridge/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'session_bridge',
hint: hint,
channel_id: siteId,
dwsgst: dwsgst,
...(dwsrst && { dwsrst: dwsrst })
})
}
);
const tokens = await response.json();
// Store tokens and establish session
}channel_idasync function refreshTokenPublic(refreshToken, clientId, siteId) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId,
channel_id: siteId // REQUIRED
})
}
);
// Returns NEW refresh_token (old one is invalidated)
return response.json();
}async function refreshTokenPrivate(refreshToken, clientId, clientSecret, siteId) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/token`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${btoa(clientId + ':' + clientSecret)}`
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
channel_id: siteId // REQUIRED
})
}
);
// Same refresh_token can be used again
return response.json();
}async function getTSOBToken(shopperLoginId) {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/trusted-system/token`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${btoa(clientId + ':' + clientSecret)}`
},
body: new URLSearchParams({
grant_type: 'client_credentials',
login_id: shopperLoginId,
channel_id: siteId,
usid: shopperUsid // Optional: reuse existing session
})
}
);
// Returns tokens that act as the specified shopper
return response.json();
}"Tenant id <id> has already performed a login operation for user id <user_id> in the last 3 seconds."async function getTSOBTokenWithRetry(shopperLoginId, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await getTSOBToken(shopperLoginId);
} catch (error) {
if (error.status === 409 && i < maxRetries - 1) {
await new Promise(r => setTimeout(r, 3000));
continue;
}
throw error;
}
}
}sfcc.ts_ext_on_behalf_oflogin_idasync function getJWKS() {
const response = await fetch(
`https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/jwks`
);
return response.json();
}const jose = require('jose');
async function validateToken(accessToken) {
// Get JWKS
const jwksUrl = `https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}/oauth2/jwks`;
const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));
// Verify token
const { payload } = await jose.jwtVerify(accessToken, JWKS, {
issuer: `https://${shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${orgId}`,
audience: clientId
});
return payload;
}| Claim | Description |
|---|---|
| Subject (customer ID or guest ID) |
| Identity subject binding |
| Issuer |
| Audience (client ID) |
| Expiration time |
| Issued at time |
| Granted scopes |
| TSOB token type (for trusted system tokens) |
channel_id