SDK (Web)
EntityAuthClient methods and usage
User roles
Read per-tenant roles for users via HTTP endpoints exposed by the dashboard app.
- GET
/api/user/roles?id=<userId>&workspaceTenantId=<tenant>→{ roles: string[] } - GET
/api/users/roles?workspaceTenantId=<tenant>&ids=<id1,id2,...>→{ rolesByUserId: Record<string,string[]> }
Example:
const res = await fetch(
`/api/user/roles?workspaceTenantId=${tenantId}&id=${userId}`,
{ headers: { Authorization: `Bearer ${accessToken}` } }
);
const { roles } = await res.json();
Configure tenant once via env (not forms)
From the dashboard → Setup tab, copy the .env snippet (tenant ID + API URL). Never ask users for workspaceTenantId in forms.
Initialize once, then construct with optional { baseURL }. All methods mirror /api/* routes and reuse the tenant ID set during init().
import { EntityAuthClient, init as initEA } from '@entityauth/auth-client';
initEA({
workspaceTenantId: process.env.NEXT_PUBLIC_ENTITY_AUTH_WORKSPACE_TENANT_ID!,
baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL,
});
const ea = new EntityAuthClient({
baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL,
});
Auth
register({ email, password, defaultWorkspaceRole? })login({ email, password })→ sets access token and returns{ userId, sessionId }refresh(headerRefreshToken?)logout()
Default workspace role on registration
Pass defaultWorkspaceRole: "owner" | "member" to assign a workspace-level role to the new user automatically. This is useful for apps that want newly registered users to be able to create organizations immediately (requires owner).
import { EntityAuthClient, init as initEA } from '@entityauth/auth-client';
initEA({
workspaceTenantId: process.env.NEXT_PUBLIC_ENTITY_AUTH_WORKSPACE_TENANT_ID!,
baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL,
});
const ea = new EntityAuthClient({ baseURL: process.env.NEXT_PUBLIC_ENTITY_AUTH_URL });
// Make the first user an owner in this workspace
await ea.register({
email: 'first@acme.com',
password: 'super-secure',
defaultWorkspaceRole: 'owner',
});If omitted, the user is created without a workspace owner/admin role; they can still sign in, but organization creation will be forbidden by policy.
Generic model helpers
- Prefer the exported
SDKhelpers for entities and relations. - Active organization is derived from
me().workspaceTenantId.
import { SDK } from '@entityauth/auth-client';
const me = await SDK.me();
const org = await SDK.createEntity({
workspaceTenantId: me.workspaceTenantId!,
kind: 'org',
properties: { name: 'Acme', slug: 'acme', ownerId: me.id },
});
await SDK.linkRelation({
workspaceTenantId: me.workspaceTenantId!,
srcId: me.id,
relation: 'member_of',
dstId: org.id,
attrs: { role: 'owner' },
});
Users
me()- Update properties via
SDK.updateEntity:
await SDK.updateEntityEnforced({ id: userId, patch: { properties: { username: 'newusername' } }, actorId: userId });
// Universal list & upsert
const list = await SDK.listEntities({ workspaceTenantId: me.workspaceTenantId!, kind: 'user', filter: { status: 'active' }, limit: 25 });
await SDK.upsertEntity({ workspaceTenantId: me.workspaceTenantId!, kind: 'user', properties: { email: 'a@b.com' } });
Sessions
- Managed server-side; use login/refresh/logout. No public list/revoke JS APIs.
Helpers
onTokenChange(listener)andgetAccessToken()getOpenAPI()to inspect the server schemagetConvexConfig()to read the Convex deployment URLapplyAccessToken(token)for external auth handoffsSDK.refresh(headerRefreshToken?)for token maintenance without the class instance
Allowed origins & cross-origin
- Localhost origins (
http://localhost:3000,3001,5173,4200) are pre-seeded for every workspace - Add production domains in the dashboard → Setup → Allowed Origins before deploying
- The middleware sets
Access-Control-Allow-Origindynamically per workspace and supportscredentials: 'include'
OpenAPI & GraphQL
const openapi = await ea.getOpenAPI();
const result = await ea.graphql<{ me: { id: string; email: string | null } }>(
`query { me { id email } }`
);
if ("errors" in result) {
console.error(result.errors);
} else {
console.log(result.data.me);
}
Best practices
Use SDK Fetch Method
Always use EntityAuthClient.fetch for authenticated requests to automatically handle token refresh on 401 responses.
// Good: Auto-refreshes on 401
const response = await ea.fetch('/api/protected-endpoint');
// Avoid: Manual fetch without auto-refresh
fetch('/api/protected-endpoint', {
headers: { Authorization: `Bearer ${token}` }
});Proactive refresh when no token
The SDK automatically attempts a refresh once before the first authenticated request if no access token is present (using the ea_refresh cookie). This avoids an initial 401/auto-retry on page reloads.
Handle Token Changes
Listen for token updates to synchronize authentication state across your application.
ea.onTokenChange((newToken) => {
if (newToken) {
console.log('User authenticated');
} else {
console.log('User logged out');
}
});Avoid Token Persistence
Never store access tokens in localStorage or sessionStorage. The SDK manages tokens in memory for security.
Server-first refresh on reload
Perform refresh on the server using the ea_refresh cookie so protected pages don't appear logged out after a hard reload. Guard routes with middleware and/or refresh in server layouts. See Framework Integration.
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const refresh = request.cookies.get('ea_refresh');
if (!refresh && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = { matcher: ['/dashboard/:path*'] };Token storage
Avoid persisting access tokens in localStorage; the SDK keeps them in memory and refreshes when needed.