Skip to main content

@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

FieldTypeDescription
idstring (ULID)Unique user identifier
emailstringUser email address
firstNamestringGiven name
lastNamestringFamily name
tenantIdstring (ULID)Tenant this user belongs to
rolesstring[]Role names assigned to this user
permissionsstring[]Effective permissions (union of all assigned roles)
emailVerifiedbooleanWhether email is verified
mfaEnabledbooleanWhether 2FA (TOTP) is active
createdAtstring (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>;
}
MethodSignatureDescription
hasPermission(permission: string) => booleanCheck exact permission OR wildcard match (crm.*)
hasRole(roleName: string) => booleanCheck 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>
ComponentGuard typeProp
<RequireAuth>Authenticated or notredirectTo?: string
<RequirePermission>Specific permissionpermission: string
<RequireRole>Specific role namerole: 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)
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 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 typeStatusWhen
unauthorized401Invalid credentials or expired token
forbidden403Authenticated but lacks required permission
validation-error422Invalid input (missing fields, weak password)
user-not-found404User does not exist in this tenant
invitation-expired410Invitation link has expired
rbac-limit-exceeded400Max roles per user (50) or permissions per role (1,000) exceeded