ctx.identity carries the authenticated user and organization for HTTP-triggered functions when Helix Identity is enabled. On paths excluded from authentication it is null instead of throwing, so handlers can serve both authenticated and anonymous requests. Schedules, app triggers, and queue consumers run without a user; accessing ctx.identity there throws a clear error.
IdentityContext
interface IdentityContext {
user: {
id: string; // Helix user ID
email: string;
name: string;
groups: string[]; // IdP-synced group names, informational only
roles: string[]; // role IDs from helix.config.ts, use for authorization
};
org: {
id: string; // Helix org ID
name: string;
};
}
ctx.identity is typed IdentityContext | null.
| Field | Type | Description |
|---|---|---|
user.id | string | Helix user ID. Also mirrored on the top-level userId execution metadata field. |
user.email | string | The user’s email address |
user.name | string | Display name |
user.groups | string[] | IdP-synced group names. For logging and display only; never branch authorization on groups. |
user.roles | string[] | Role IDs declared in helix.config.ts, resolved from direct assignments plus group-to-role mappings. The authorization source. |
org.id | string | Helix organization ID |
org.name | string | Organization name |
Roles are the app’s authorization vocabulary; groups are an IdP concern. The platform maps IdP groups to roles, and your code should only reason in roles: declarative restrictions via the access named export, or user.roles.includes(...) for branching inside a handler.
Example
// functions/api/v1/dashboard.get.ts
import { defineFunction } from '@trayai/helix-sdk';
export default defineFunction(async (ctx) => {
if (!ctx.identity) {
// Reachable only on paths excluded from authentication
throw ctx.error(401, 'Sign-in required');
}
const { user, org } = ctx.identity;
ctx.log.info('Dashboard accessed', { userId: user.id, org: org.name });
const isAdmin = user.roles.includes('admin');
return isAdmin ? getFullDashboard(ctx) : getLimitedDashboard(ctx);
});
Identity in local development
helix dev has no SSO flow in front of it. The dev server injects a static dev identity into every request, so ctx.identity is always populated locally. The default dev user is dev-user / dev@localhost. To test as a specific persona, pass an identity JSON file with helix dev --identity <path>. A file containing just null simulates an unauthenticated request.
Who can open a published app is set on the project’s Access Control tab, not in code. See identity and roles.