@platform/sdk-auth
@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-auth
useAuth()
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({
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 tabs
useRBAC()
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 true
requireRole()
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 verification
resetPassword()
Initiate or complete the password reset flow:
import { resetPassword } from '@platform/sdk-auth';
// Step 1: Send reset email (unauthenticated call)
// → 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 revoked
inviteUser()
Invite a new user to join the current tenant:
import { inviteUser } from '@platform/sdk-auth';
await inviteUser({
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.create
registerProvider()
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 |