Skip to content

Identity

Ada signs in to your task application. The header should say “Hello, Ada”, the Archive button should only appear for editors, and the page should know which customer she is working for. Without help you end up writing a /me route, repeating the role list in the frontend, and deciding by hand what the browser may cache.

Arc gives you one endpoint for that job. You write a small provider that turns the authenticated caller into the details your UI needs. Arc serves the result at GET /.cratis/me and sets a cookie that the published @cratis/arc client reads, so every component can ask “who is this?” without another request.

Identity details sit next to two other concerns. Keep them apart, because each one trusts different evidence:

JobQuestionWhere it lives
AuthenticationWho is calling?Authentication handlers or a native principal
AuthorizationMay this caller run this command or query?Decorators and policies on each operation
Identity detailsWhat should the UI show about this caller?An identity details provider, served at /.cratis/me

Request with a credential

Authentication handler

Verified principal

Operation authorization

Identity details provider

/.cratis/me JSON and cookie

Frontend display

The provider only ever sees a principal that authentication already verified. Nothing it returns flows back into authorization.

Write a provider class and let discovery find it, or add it with builder.add(...):

Features/Identity/GreetingDetails.ts
import { field } from '@cratis/fundamentals';
import {
identityDetailsProvider,
type ExecutionContext, type IdentityDetailsProvider, type Principal
} from '@cratis/arc.core';
export class UserDetails {
@field(String) greeting!: string;
}
@identityDetailsProvider()
export class GreetingDetails implements IdentityDetailsProvider {
readonly detailsType = UserDetails;
provide(principal: Principal, context: ExecutionContext): UserDetails {
return { greeting: `Hello ${principal.name ?? principal.id} (${context.tenantId ?? 'no tenant'})` };
}
}

detailsType tells Arc the shape of the details, so it can validate what provide returns, describe it at /.cratis/identity-details/schema, and let the proxy generator emit a matching frontend class.

With an authentication handler that recognizes Ada and a request for tenant acme, GET /.cratis/me answers:

{"id":"ada","name":"Ada","isAuthenticated":true,"isAuthorized":true,"roles":["Editor"],"details":{"greeting":"Hello Ada (acme)"}}

The same response sets .cratis-identity=<base64>; Path=/; SameSite=Lax. The id, name, and roles come from the verified principal. Only details comes from your provider, and Arc runs it again on every call to /.cratis/me.

  • Authentication decides who the caller is. Your provider only adds display details.
  • /.cratis/me returns the principal plus those details, and caches them in a cookie for the frontend.
  • Commands and queries keep their own authorization. Hiding a button in the UI protects nothing.
TopicWhat it covers
How identity details are servedRegistration choices, every /.cratis/me answer, the cookie format, and how caching works
Identity contractsThe provider contract, the principal it receives, the /.cratis/me shape, and the differences from Arc on .NET
Entra API bearer tokensTenant-specific JWT verification, app roles and delegated scopes
AuthProxy and EasyAuthForwarded principals, ingress isolation and durable user keys
Observable transport authenticationCredentials on WebSocket upgrades and SSE requests, without named schemes
Show identity in a React frontenduseIdentity, RequireRole, typed details, and refreshing after a change
Identity across servicesOne service, several services behind a gateway, or a dedicated identity service
Simulate a signed-in user locallyTry different users, roles, and tenants on a loopback development host
Development users and tenantsFixture lists for local user and tenant pickers

Next, read how identity details are served to see what happens between the request and the cookie.