@platform/sdk-auth
sdk-auth provides authentication and RBAC primitives for module authors. Exports: useAuth() (user state, login, logout, token refresh), useRBAC() (hasPermission, hasRole), requireRole() HOC guard, verifyEmail(), resetPassword(), inviteUser(), registerProvider(), listProviders(). Guard components: <RequireAuth>, <RequirePermission>, <RequireRole>.
@platform/sdk-auth provides authentication state, RBAC permission
checks, user management operations, and auth provider registration for
modules. All methods are fully typed with TypeScript.
Installation
pnpm add @platform/sdk-authuseAuth()
React hook that exposes the current user's authentication state and auth operations. Re-renders the component when auth state changes.
import { useAuth } from '@platform/sdk-auth';
function Header() {
const {
user, // Current user object or null
isLoading, // true during initial auth check
isAuthenticated,
login,
logout,
} = useAuth();
if (isLoading) return <Spinner />;
if (!isAuthenticated) return <LoginPrompt />;
return <div>Welcome, {user.email}</div>;
}User Object
| Field | Type | Description |
|---|---|---|
id | string (ULID) | Unique user identifier |
email | string | User email address |
firstName | string | Given name |
lastName | string | Family name |
tenantId | string (ULID) | Tenant this user belongs to |
roles | string[] | Role names assigned to this user |
permissions | string[] | Effective permissions (union of all assigned roles) |
emailVerified | boolean | Whether email is verified |
mfaEnabled | boolean | Whether 2FA (TOTP) is active |
createdAt | string (ISO 8601) | Account creation timestamp |
login()
const { login } = useAuth();
await login({
email: '[email protected]',
password: 'securepassword',
});
// On success: user state updates, token stored, redirect handled
// On failure: throws PlatformError (status 401)logout()
const { logout } = useAuth();
await logout();
// Clears tokens (memory + localStorage)
// POST /auth/logout → invalidates refresh token server-side
// BroadcastChannel notifies all tabsuseRBAC()
React hook for permission and role checks based on the current user's effective RBAC state:
import { useRBAC } from '@platform/sdk-auth';
function PayoutButton() {
const { hasPermission, hasRole } = useRBAC();
if (!hasPermission('finance.payouts.approve')) {
return null; // Hide component entirely for unauthorized users
}
return <button>Approve Payout</button>;
}| Method | Signature | Description |
|---|---|---|
hasPermission | (permission: string) => boolean | Check exact permission OR wildcard match (crm.*) |
hasRole | (roleName: string) => boolean | Check if user has a specific role by name |
Permission Resolution
The SDK checks permissions with wildcard support. When the user has
crm.* in their role, hasPermission('crm.contacts.read') returns
true without needing the explicit granular permission:
hasPermission('crm.contacts.read'):
1. Check exact: 'crm.contacts.read' in user.permissions → true/false
2. If not found: check wildcard: 'crm.*' in user.permissions → true/false
3. Platform Owner ('*'): always truerequireRole()
Higher-Order Component (HOC) that wraps a React component and renders it only when the current user has the specified role or permission. Non-authorized users receive a fallback UI:
import { requireRole } from '@platform/sdk-auth';
const AdminPanel = requireRole('admin', { fallback: <AccessDenied /> })(
() => <div>Admin content here</div>
);// Permission-based guard (preferred for fine-grained control)
const PayoutApproval = requireRole({
permission: 'finance.payouts.approve',
fallback: <p>You do not have permission to approve payouts.</p>,
})(PayoutApprovalComponent);Guard Components
Declarative JSX alternatives to requireRole():
import { RequireAuth, RequirePermission, RequireRole } from '@platform/sdk-auth';
// Redirect to login if unauthenticated
<RequireAuth redirectTo="/login">
<Dashboard />
</RequireAuth>
// Render only when user has permission
<RequirePermission permission="reports.export" fallback={<p>No access</p>}>
<ExportButton />
</RequirePermission>
// Render only when user has role
<RequireRole role="billing-admin" fallback={null}>
<BillingPanel />
</RequireRole>| Component | Guard type | Prop |
|---|---|---|
<RequireAuth> | Authenticated or not | redirectTo?: string |
<RequirePermission> | Specific permission | permission: string |
<RequireRole> | Specific role name | role: string |
verifyEmail()
Trigger email verification for the currently authenticated user:
import { verifyEmail } from '@platform/sdk-auth';
await verifyEmail();
// Sends verification email via Notify primitive
// User clicks link in email → POST /auth/verify-email?token=...
// user.emailVerified becomes true after verificationresetPassword()
Initiate or complete the password reset flow:
import { resetPassword } from '@platform/sdk-auth';
// Step 1: Send reset email (unauthenticated call)
await resetPassword.request({ email: '[email protected]' });
// → POST /auth/password-reset/request
// → Notify sends email with time-limited reset link
// Step 2: Submit new password (from reset link)
await resetPassword.confirm({
token: 'reset-token-from-email',
newPassword: 'NewSecurePass123!',
confirmPassword: 'NewSecurePass123!',
});
// → POST /auth/password-reset/confirm
// → All active refresh tokens for this user are revokedinviteUser()
Invite a new user to join the current tenant:
import { inviteUser } from '@platform/sdk-auth';
await inviteUser({
email: '[email protected]',
roleIds: ['01j9prole000000000000000'],
message: 'Welcome to our workspace!', // Optional personal message
});
// → POST /api/v1/users/invitations
// → Notify sends invitation email with registration link
// Requires permission: users.createregisterProvider()
Register a custom authentication provider adapter from within a module. The provider appears automatically on the platform login page and in Settings → Authentication:
import { registerProvider } from '@platform/sdk-auth';
registerProvider({
id: 'metamask',
name: 'MetaMask',
type: 'web3',
icon: 'metamask-icon',
description: 'Sign in with your Ethereum wallet',
callbackUrl: '/auth/metamask/callback',
config: {
chainId: 1, // Ethereum mainnet
message: 'Sign this message to authenticate with the platform.',
},
});
// Registered via module.manifest.json (authProviders[]) at install time.
// Also callable programmatically at runtime.Provider registration is idempotent — calling registerProvider() with
the same id updates the existing provider record.
listProviders()
Retrieve all authentication providers enabled for the current tenant:
import { listProviders } from '@platform/sdk-auth';
const providers = await listProviders();
// Returns:
// [
// { id: 'email', name: 'Email & Password', type: 'builtin', enabled: true },
// { id: 'google', name: 'Google', type: 'oauth2', enabled: true },
// { id: 'metamask', name: 'MetaMask', type: 'web3', enabled: false },
// ]The built-in email provider cannot be disabled — it is the
fallback authentication method for all tenants.
Error Reference
| Error type | Status | When |
|---|---|---|
unauthorized | 401 | Invalid credentials or expired token |
forbidden | 403 | Authenticated but lacks required permission |
validation-error | 422 | Invalid input (missing fields, weak password) |
user-not-found | 404 | User does not exist in this tenant |
invitation-expired | 410 | Invitation link has expired |
rbac-limit-exceeded | 400 | Max roles per user (50) or permissions per role (1,000) exceeded |
@platform/sdk-core
sdk-core is the foundation of the Platform SDK. Provides the HTTP client (Fetch API with automatic retries), RFC 9457 error parsing into typed PlatformError, JWT access + refresh token management with BroadcastChannel cross-tab coordination (10s grace window), and request/response interceptors for logging, auth header injection, and tracing.
@platform/sdk-data
sdk-data wraps the Platform Data Layer. Provides useQuery() and useMutation() hooks (TanStack Query under the hood), useRealtime() for WebSocket-driven live updates, PostgREST-style filtering, optimistic updates, cache invalidation, keyset-based pagination, and eager-loading via include. Backed by PostgreSQL (OLTP) + ClickHouse (OLAP) + Valkey.