01 Software

Customer Authentication

Customer registration, login, token management, password reset, and email verification

Customer Authentication

The SDK provides client.customer for storefront customer authentication — separate from the admin/tenant authentication system.

Customer auth is available on Client only. For server-side customer data management, use client.from('customers') with ServerClient.

Setup

lib/client.ts
import { createClient } from '@01.software/sdk'

export const client = createClient({
  publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
  customer: { persist: true },
})

The persist option automatically saves/restores the token in localStorage (SSR-safe). You can also use a custom storage key:

customer: { persist: 'my-app-token' }  // custom localStorage key

For full control, use token and onTokenChange instead:

customer: {
  token: getTokenFromCookie(),
  onTokenChange: (token) => setTokenCookie(token),
}
OptionTypeDescription
persistboolean | stringAuto-persist in localStorage. true uses key 'customer-token', string for custom key
tokenstringInitial token (e.g. from SSR cookie). Ignored when persist is set
onTokenChange(token: string | null) => voidCalled on login/logout. Ignored when persist is set

Register

const { customer, verificationRequired } = await client.customer.register({
  name: 'John Doe',
  email: 'john@example.com',
  password: 'securepassword',
  phone: '010-1234-5678',   // optional
})

if (verificationRequired) {
  // Tenant requires email verification — token delivered via webhook
  // Show "check your email" message
}

Registration does not automatically log in. Call login() after registration.

If the tenant has requireEmailVerification enabled, the response includes verificationRequired: true and a verification token is dispatched to the tenant's webhook URLs.

Login

const { token, customer } = await client.customer.login({
  email: 'john@example.com',
  password: 'securepassword',
})

// Token is stored internally and onTokenChange is called

The returned customer object:

{
  id: string
  name: string
  email?: string | null
  phone?: string | null
  authProvider?: 'local' | 'google' | 'apple' | 'kakao' | 'naver' | null
  isGuest?: boolean | null
  isVerified?: boolean | null
  marketingConsent?: boolean | null
  metadata?: Record<string, unknown> | null
  groups?: string[]
}

Profile

const profile = await client.customer.me()

if (!profile) {
  // Not authenticated or token expired
}

Returns null if not authenticated. Automatically clears the token on 401.

Logout

client.customer.logout()
// Token cleared, onTokenChange called with null

Password Reset

1. Request reset email

await client.customer.forgotPassword('john@example.com')
// Always succeeds (prevents email enumeration)

2. Reset with token

The reset email contains a link with a token. Use it to set a new password:

await client.customer.resetPassword(token, 'newpassword')

Update Profile

Update the authenticated customer's profile. Only name, phone, and marketingConsent can be modified.

await client.customer.updateProfile({
  name: 'Jane Doe',
  phone: '010-9876-5432',
  marketingConsent: true,
})

Change Password

Requires authentication:

await client.customer.changePassword('currentPassword', 'newPassword')

Email Verification

await client.customer.verifyEmail(token)

Refresh Token

Extend the current session by refreshing the JWT token:

const { token } = await client.customer.refreshToken()
// New token is stored internally and onTokenChange is called

My Orders

Fetch the authenticated customer's order history:

const { docs: orders } = await client.customer.getMyOrders({
  page: 1,
  limit: 10,
  status: 'paid',  // optional filter
})

Token Management

// Check authentication status
client.customer.isAuthenticated()  // boolean

// Get current token
client.customer.getToken()  // string | null

// Set token manually (e.g. from SSR)
client.customer.setToken(token)

Guest vs Registered

GuestRegistered
Creationclient.from('customers').create({ ... }) via ServerClientclient.customer.register({ ... })
LoginNot possibleclient.customer.login()
Cartcustomer field omitted or linkedLinked to customer ID
Order historyLookup by email/order numberclient.customer.me() + scoped queries
Address bookNot availableFull CRUD

Error Handling

import { ApiError } from '@01.software/sdk'

try {
  await client.customer.login({ email, password })
} catch (error) {
  if (error instanceof ApiError) {
    switch (error.status) {
      case 401:
        // Invalid credentials
        break
      case 429:
        // Rate limited
        break
    }
  }
}

On this page