Skip to main content
A session is the typed, per-request object your server code calls can() on. It carries an authenticated actorUserId and, optionally, a view-as state for admin preview. Sessions are the bridge between your identity provider (Clerk, Auth.js, or any other) and Alfiz’s permission evaluation. Alfiz does not issue or validate tokens; you verify the token through your provider and pass the resulting user ID to createSession.

Creating a session

SessionOptions

string
required
The ID of the authenticated user, as issued by your identity provider. This is the user whose real permissions are checked. It is never replaced by a view-as state for access decisions.
ViewAsState | null
Activates a preview session. Pass null (or omit) for a normal session. Activating a preview is gated for you: when viewAs is non-null, createSession calls assertCanViewAs and throws AccessDeniedError unless the actor holds alfiz_internal.access.view_as. A catalog built with includeAlfizInternal: false declares no such key, so previews fail closed rather than erroring.

Return value

AlfizSession<K, P, S>
A session object with can, canAny, require, and requireAny methods. All checks are preview-narrowed: a view-as session can only see access that both the actor and the previewed subject hold. A preview can narrow what is shown; it can never escalate privileges.

Session methods

Every check on a session intersects the actor’s access with the previewed subject’s, so a preview can only ever narrow. It can never show the viewer a surface their own grants would not reach.
Always use actorUserId for audit attribution. It is never the previewed subject. Use subjectUserId for data-scoped surfaces (e.g., “show documents owned by this user”), and it returns the previewed user’s ID during a user preview, and the actor’s ID otherwise.

session.snapshot(): the render path under view-as

The async session methods are fine for a gate, but a render path cannot await inside .map(). session.snapshot() is the session-shaped version of the per-request snapshot: one fetch per identity, then synchronous, preview-narrowed checks.
options, meaning pre-resolved scopes and fresh, applies to both identities at once, so the actor and the preview are always read at the same instant.
A role preview evaluates synchronously with no extra resolution, because role patterns are global-scope by construction. An unknown role id fails closed.
Denials thrown from a session snapshot name the actor, not the previewed identity, so attribution never follows the preview. The same is true of metrics: an administrator looking through someone’s eyes did not use that person’s grants, so the previewed side is recorded with observe: false.
Annotate a stored session snapshot with SessionSnapshotOf<typeof catalog>, which completes the derived-type family alongside ClientOf, SnapshotOf, and SessionOf.

Derived session type

Use SessionOf<typeof catalog> to type session variables without writing out the K, P, and S type parameters by hand:

Wiring to Clerk (Next.js example)

Alfiz does not provide a Clerk adapter package. You write a thin getAlfizSession helper that calls Clerk’s auth() and passes the user ID to createSession. This keeps the integration explicit and avoids a hard dependency on any specific Clerk version.

View-as: admin preview

Administrators holding alfiz_internal.access.view_as can preview the product as either a role or an individual user. createSession enforces this gate automatically, so passing a non-null viewAs without the permission throws AccessDeniedError.
Serialize and deserialize view-as state for cookie storage:
Stopping a preview is deliberately ungated (anti-lockout). Never require view_as to exit a preview session. Build the “exit preview” button to call createSession with viewAs: null.

assertCanViewAs

If you need to check preview eligibility independently (for example, to show or hide the “view as” UI affordance), call assertCanViewAs directly:
Throws AccessDeniedError when the actor does not hold alfiz_internal.access.view_as, or when the catalog was built with includeAlfizInternal: false.

Service principals

For machine-to-machine authentication in background jobs, CI pipelines and internal services, Alfiz provides a local service-key shim that validates shared bearer tokens server-side.

Creating the shim

Verifying a service request

A verified service key yields the machine subject service:<serviceId>. Grant to it like any other subject:

The env format

parseServiceKeysEnv parses ALFIZ_SERVICE_KEYS in the format:
Keep the previous key present during rotation, then remove it after confirming the new key is deployed everywhere.

Key length requirement

Keys must be at least 16 characters. createServiceKeyShim throws at startup if any key is shorter.

Client-reach guard

Service keys must never reach the browser. Add ALFIZ_SERVICE_KEYS to forbidClientIdentifiers in your alfiz-verify.config.json so the build fails if the key module ever becomes client-reachable:
See Verify Config for the full configuration reference.