| name | auth-and-identity |
| description | Use this skill for authentication, authorization, or identity in a Cratis Arc project — backend, frontend, or both. Covers identity providers (`IProvideIdentityDetails`), protecting commands/queries with authorization attributes, Microsoft Identity Platform, connecting backend identity to React, multi-tenant identity, and local-dev generated principals. Trigger on auth, login, roles, permissions, identity details, user context, or protecting endpoints. |
Auth & Identity in Cratis Arc
This skill covers the full auth and identity stack in a Cratis Arc application. Read the relevant reference files below for detailed API usage.
Read the relevant instruction files first. This skill references concepts from the core copilot instructions in .github/copilot-instructions.md. If you need details on vertical slices, commands, queries, or proxy generation, consult those instructions.
Architecture Overview
Identity and auth in Arc follow a cookie-first, convention-based pattern:
Frontend (React) Backend (ASP.NET Core)
───────────────── ──────────────────────
<IdentityProvider> app.MapIdentityProvider()
└── useIdentity() hook └── GET /.cratis/me
│ │
├─ 1. Read .cratis-identity cookie │
└─ 2. If no cookie → fetch /.cratis/me │
▼
AuthenticationMiddleware
└── IAuthenticationHandler[]
│ sets HttpContext.User
▼
IIdentityProvider.Get()
└── IProvideIdentityDetails.Provide()
│
▼
IdentityProviderResult
→ JSON response
→ .cratis-identity cookie (base64)
Authorization:
[Authorize] / [Roles("Admin")] / [AllowAnonymous]
└── AuthorizationEvaluator checks per command/query
Key design decisions:
- The
.cratis-identity cookie is HttpOnly=false so the frontend JavaScript can read it directly — no extra HTTP call needed on page load.
- Identity details are base64-encoded JSON in the cookie, automatically decoded by the frontend
IdentityProvider.
- Only one
IProvideIdentityDetails implementation is allowed per application (auto-discovered). If none exists, a default provider grants access to everyone.
- Cratis has its own
[Authorize], [AllowAnonymous], and [Roles] attributes in Cratis.Arc.Authorization — these are distinct from ASP.NET Core's and are evaluated by AuthorizationEvaluator in the command and query pipeline.
Decision Tree — Which Reference to Read
Use this decision tree to determine which reference file(s) to read based on the user's task:
For full-stack tasks, read in this order:
references/backend-identity.md — identity provider and startup
references/authentication.md — how users are authenticated
references/authorization.md — protecting commands and queries
references/frontend.md — consuming identity in the UI
references/local-development.md — testing without real infrastructure
Quick-Start: Full-Stack Identity Setup
This is the minimum checklist for an application with identity. Read the reference files for details on each step.
Backend
- Details record: Define a C# record for application-specific user information
- Identity provider: Implement
IProvideIdentityDetails<TDetails> (auto-discovered, one per app)
- Startup: Call
app.MapIdentityProvider() to register GET /.cratis/me
- Authentication: Add
AddMicrosoftIdentityPlatformIdentityAuthentication() or implement IAuthenticationHandler
- Authorization: Add
[Authorize] / [Roles] / [AllowAnonymous] attributes from Cratis.Arc.Authorization to commands and queries
Frontend
- Provider: Wrap app root with
<IdentityProvider> from @cratis/arc.react/identity
- Hook: Use
useIdentity<TDetails>() to access identity anywhere in the component tree
- Roles: Use
identity.isInRole('Admin') for UI-level role gating
Proxy Generation
- Using
IProvideIdentityDetails<TDetails> (generic) enables automatic TypeScript type generation at dotnet build time — the generated types can be imported in the frontend for end-to-end type safety.
Critical Rules
These rules are frequently violated — always enforce them:
- One identity provider per app: Only one
IProvideIdentityDetails implementation is allowed. Multiple throws MultipleIdentityDetailsProvidersFound.
- Use Cratis attributes, not ASP.NET Core's:
[Authorize], [Roles], [AllowAnonymous] must come from Cratis.Arc.Authorization, not Microsoft.AspNetCore.Authorization.
- Never combine
[Authorize] and [AllowAnonymous] on the same target: This throws AmbiguousAuthorizationLevel.
- Prefer the generic interface: Use
IProvideIdentityDetails<TDetails> over IProvideIdentityDetails to enable proxy generation.
- Auto-discovery: Both
IProvideIdentityDetails and IAuthenticationHandler implementations are auto-discovered — no DI registration needed.
- Frontend role checks are UX, not security:
isInRole() on the frontend hides UI elements. The backend [Roles] attribute is the actual security boundary.
- Build before frontend: TypeScript proxy types for identity details are generated by
dotnet build. The backend must compile before the frontend can import them.
- Authorize at the boundary, secure by default: express access with
[Authorize]/[Roles] attributes on the command/query — never gate behavior with an if (identity.IsInRole(...)) inside Handle(). For app-wide protection, configure a default-deny fallback policy so a target is protected unless it explicitly opts out with [AllowAnonymous]. Cross-cutting auth that spans many commands belongs in an ICommandFilter, not duplicated per handler.
- Command-specific scope is validation, not an attribute: a rule like "may only act on resources in your own organization" belongs in the
CommandValidator<T> (inject the identity and reject with a validation error) — not in Handle(), and not expressible by a role attribute alone.
- Read the current user from the authenticated principal: in backend code resolve the current user from
IHttpContextAccessor.User (the ASP.NET principal the authentication handler populates). Do not invent a bespoke IIdentityAccessor abstraction — that is a product-specific wrapper, not part of generic Cratis.
Common Code Patterns
Protecting a command with roles
[Command]
[Roles("Admin")]
public record PromoteUser(UserId Id)
{
public void Handle(IUserService users) => users.Promote(Id);
}
Conditionally rendering UI based on roles
const identity = useIdentity();
return identity.isInRole('Admin')
? <AdminPanel />
: <AccessDenied />;
Modifying identity at runtime (stateless selections)
public class SetDepartment(IIdentityProvider identityProvider)
{
public async Task Handle(string department) =>
await identityProvider.ModifyDetails<UserDetails>(
details => details with { SelectedDepartment = department });
}
Reference Documentation
Skill references (detailed implementation guidance)
- Backend Identity Provider —
IProvideIdentityDetails, IdentityProviderContext, cookie mechanics, proxy generation, ModifyDetails
- Authentication —
IAuthenticationHandler, AuthenticationResult, Microsoft Identity Platform, combining handlers
- Authorization —
[Authorize], [Roles], [AllowAnonymous], inheritance rules, fallback policies
- Frontend Identity — React
IdentityProvider, useIdentity(), MVVM, core identity, role checking
- Local Development — Generating principals, ModHeader, cookie fallback, dev testing