Skip to content

Authentication

Customer authentication lets readers register and log in on your site with email and password. The SDK manages JWT tokens in localStorage and automatically attaches them to access checks for verified identity.

Initialize the SDK as usual. Auth methods use the same apiKey and apiUrl:

import { init } from '@latch/sdk';
init({
apiKey: 'pk_...',
apiUrl: 'https://your-latch-api',
});
import { register } from '@latch/sdk';
try {
const { customer, accessToken } = await register(
'securepass123',
'Jane Doe', // optional name
);
console.log('Registered:', customer.email);
} catch (err) {
// err.message contains the API error (e.g. "A customer with this email already exists")
console.error(err.message);
}

After registration, the SDK stores the access token and refresh token in localStorage. Subsequent checkAccess() calls automatically include the verified JWT.

import { login } from '@latch/sdk';
try {
const { customer } = await login('[email protected]', 'securepass123');
console.log('Logged in as:', customer.email);
} catch (err) {
console.error(err.message); // "Invalid email or password"
}
import { logout } from '@latch/sdk';
await logout();
// Refresh token is revoked on the server
// All stored tokens are cleared
import { isAuthenticated, getStoredCustomer, getSession } from '@latch/sdk';
// Synchronous check (no network request)
if (isAuthenticated()) {
const customer = getStoredCustomer();
console.log('Welcome back,', customer?.name);
}
// Server-validated profile (refreshes token if needed)
const profile = await getSession();
if (profile) {
console.log(profile.email, profile.customAttributes);
}

Listen for login, logout, and token refresh events:

import { onAuthChange } from '@latch/sdk';
const unsubscribe = onAuthChange((customer) => {
if (customer) {
showUserMenu(customer.name || customer.email);
} else {
showLoginButton();
}
});
// Later: stop listening
unsubscribe();

When the customer is authenticated, checkAccess() automatically attaches the JWT:

import { checkAccess } from '@latch/sdk';
const result = await checkAccess();
// The API verifies the JWT and uses the customer's real ID
// for subscription lookups and paywall evaluation

If the access token is expired, the SDK silently refreshes it before the access check. If the refresh token is also expired, the customer is treated as anonymous.

TokenStorageExpiryRenewal
Access token (JWT)localStorage15 minutesAutomatic via refresh token
Refresh tokenlocalStorage30 daysRotated on each use (old token invalidated)

On page load, the SDK restores auth state from localStorage. If the access token is expired, it is silently refreshed on the next checkAccess() or getSession() call.

<script type="module">
import {
init,
login,
register,
logout,
onAuthChange,
checkAccess,
isAuthenticated,
} from '@latch/sdk';
init({ apiKey: 'pk_...', apiUrl: 'https://your-latch-api' });
// Update UI on auth changes
onAuthChange((customer) => {
document.getElementById('auth-status').textContent =
customer ? `Hello, ${customer.name || customer.email}` : 'Not logged in';
});
// Registration form
document.getElementById('register-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const form = new FormData(e.target);
try {
await register(form.get('email'), form.get('password'), form.get('name'));
} catch (err) {
alert(err.message);
}
});
// Login form
document.getElementById('login-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const form = new FormData(e.target);
try {
await login(form.get('email'), form.get('password'));
// Access checks now use verified identity
const access = await checkAccess();
console.log('Access:', access.granted);
} catch (err) {
alert(err.message);
}
});
// Logout button
document.getElementById('logout-btn')?.addEventListener('click', () => logout());
</script>
  • Auth endpoints accept publishable keys (pk_), so the SDK can call them directly from the browser. The customer’s password is the real credential.
  • Passwords are hashed with Argon2id before storage.
  • Refresh tokens are stored as SHA-256 hashes in the database.
  • Refresh tokens are rotated on use — each refresh invalidates the old token.
  • JWTs are signed with HMAC-SHA256 and verified on every access check.
  • Login returns the same error for wrong password and non-existent email to prevent email enumeration.
  • Customers are scoped to a publication — a customer on site A cannot authenticate on site B.