Skip to content

Authentication rendering

A model’s authentication block names the identity providers an application signs people in with. The Cratis renderer turns it into an application that runs behind Cratis AuthProxy and a configuration AuthProxy starts from.

For a model that declares

authentication
provider EntraId
provider GitHub
provider OpenId name Partner

an application plan adds the following to the application:

FileWhat it is
appsettings.jsonCratis:Arc:TrustForwardedIdentityHeaders is true, so Arc builds the caller from the identity headers AuthProxy forwards.
Program.csCalls RuntimeConventions.Apply before Cratis reads its configuration.
GeneratedAuthentication/RuntimeConventions.csReads the Chronicle connection string from the file named by CRATIS_CHRONICLE_CONNECTION_STRING_FILE.
GeneratedAuthentication/Personas.csThe personas of the model with the roles and claims that satisfy the policies each one holds.
GeneratedAuthentication/MemberIdentity.csThe identity of a person: the provider they sign in with and their subject.
.runtime/authproxy.application.jsonThe provider list in AuthProxy’s Cratis:AuthProxy:Application shape.
.runtime/environment.jsonThe environment keys the model needs, with the secrets marked.

The identity headers are not signed. Arc ignores them unless TrustForwardedIdentityHeaders is on, and AuthProxy removes every identity header a client sends and writes its own, so the setting is right only when every request reaches the application through AuthProxy. Never expose a rendered application with authentication directly.

The policies the commands and queries already carry need nothing else. A caller AuthProxy forwarded satisfies require authenticated, require role and require claim through the roles and claims in the forwarded principal; an anonymous caller satisfies none of them, and an authorize Anyone policy still renders as [AllowAnonymous].

cratis run and Stage Manager find the runtime files in the .runtime folder of the rendered application and add what only they know: the environment name, the secrets directory, Development and the values of the environment.

{
"Cratis": {
"AuthProxy": {
"Application": {
"Providers": [
{ "Kind": "EntraId", "Identity": "EntraId" },
{ "Kind": "GitHub", "Identity": "GitHub" },
{ "Kind": "OpenId", "Identity": "Partner" }
]
}
}
}
}

Onboarding is true in the same file when the model declares onboarding. The kinds are EntraId, GitHub, Google, Apple and OpenId; a provider of another kind is reported as STAGE-AUTH-001 and left out. RuntimeConfigurationFiles.Render produces the same files for a caller that holds only a realization.

The application container receives:

VariableMeaning
CRATIS_ENVIRONMENTThe name of the environment.
CRATIS_SECRETS_DIRECTORYA directory with one file per environment key, named by the key.
CRATIS_CHRONICLE_CONNECTION_STRING_FILEA file holding the Chronicle connection string.

A persona holds policies. Personas.cs records, per persona, the smallest set of roles and claims that satisfies all of its policies, in the order Screenplay’s persona callers use. Personas.Holds is true for a caller who has all of them. A persona whose policies are not requirements on authentication, roles or claims cannot be reduced to roles and claims, is never matched and is reported as STAGE-AUTH-002.

The realization is not part of the executable model, so a caller adds it to the profile:

var profile = CratisRendering.WithRealization(CratisRendering.CreateProfile(name, options), compilation, modelRoot);

compilation is the SemanticCompilation the plan is made from. modelRoot is the folder holding the .play files, from which the images of onboarding emails are read; leave it out when there are none. SemanticModelLoader does the same for LoadedSemanticModel.Realization, and CratisRendering.PlanFrom adds it itself. The realization is a profile input, so it is part of the input digest.

cratis run starts the Stage sandbox behind AuthProxy when the model has authentication. Set Stage:Sandbox:ForwardedIdentity (environment variable Stage__Sandbox__ForwardedIdentity) to true and the sandbox trusts the forwarded identity exactly as a rendered application does: it plays no persona and the signed-in caller is the caller its authorization runs for. Without the setting, the sandbox keeps playing a persona and removes the identity headers a caller sends.

The syntax-based writer that IRenderer exposes writes .runtime/authproxy.application.json and .runtime/environment.json, and reports a provider kind AuthProxy does not know. It does not change the scaffold, so the application it writes does not trust the forwarded identity; use cratis render for that.