MMLib.Alvo.Identity
ASP.NET Core Identity for Alvo: administrator accounts, roles, cookie sign-in and the bootstrap administrator.
ASP.NET Core Identity for Alvo: administrator accounts, roles, cookie sign-in and the bootstrap administrator.
Namespace MMLib.Alvo.Identity
Section titled “Namespace MMLib.Alvo.Identity”AlvoIdentity
Section titled “AlvoIdentity”static class
The identity subsystem’s two wire-level names: its configuration section and its DI key.
ConfigurationSection
Section titled “ConfigurationSection”public const string ConfigurationSection = "Alvo:Admin";The configuration section the bootstrap administrator is configured from.
ResolverKey
Section titled “ResolverKey”public const string ResolverKey = "alvo-identity";The DI key the cookie IAlvoContextResolver is registered under.
Keyed, and that is a security decision rather than a composition style. The unkeyed IAlvoContextResolver is what the Data API hands the raw X-Alvo-Api-Key header to. This resolver’s credential is a subject identifier ASP.NET Core has already authenticated, so reaching it from that header would turn “knows an operator’s uuid” into a working credential. Registering it under a key means the header path cannot resolve it at all.
AlvoIdentityAuthenticationExtensions
Section titled “AlvoIdentityAuthenticationExtensions”static class
Cookie sign-in for the humans who administer a project.
Separate from AddAlvoIdentity on purpose. That one registers the store and the context resolver — everything an embedded host needs to resolve its own already authenticated users through Alvo. This one adds an authentication scheme, which is a decision about the whole application: a host that already has cookie authentication of its own must not have a second scheme registered underneath it.
AddAlvoIdentityCookieSignIn
Section titled “AddAlvoIdentityCookieSignIn”public static IServiceCollection AddAlvoIdentityCookieSignIn(this IServiceCollection services, string signInPath, string? accessDeniedPath = null)Adds the cookie scheme, the sign-in manager and AlvoSignIn.
The cookie is HttpOnly, SameSite=Lax and SecurePolicy=SameAsRequest. Lax rather than Strict because a sign-in that followed a link from anywhere else would otherwise land on the sign-in page again; SameAsRequest rather than Always because the zero-configuration first run is http://localhost and a cookie the browser refuses to store is a sign-in that silently never completes. A deployment behind TLS gets Secure from the request it is actually serving.
The unauthenticated redirect goes to signInPath and the caller supplies it, because the screen belongs to whoever is rendering one — MMLib.Alvo.Admin in the standalone image, the host’s own page in an embedded one.
The circuit’s provider is registered last-wins, and a host decides which one it gets. This method adds AuthenticationStateProvider with a plain AddScoped, so it replaces a provider the host registered before calling it, and a provider the host registers after it replaces this one — and with it the circuit’s re-check. A host with a provider of its own should register it after this call and make it revalidate (deriving from RevalidatingServerAuthenticationStateProvider); a host that wants this one should register none. Either way the cookie re-check stays, and every management and data call still re-resolves its caller.
services— The service collection to register into.signInPath— Where an unauthenticated request is sent.accessDeniedPath— Where an authenticated but unauthorized request is sent.
Returns: services, for chaining.
AlvoIdentityOptions
Section titled “AlvoIdentityOptions”class
The identity subsystem’s own configuration, bound from ConfigurationSection.
BootstrapEmail
Section titled “BootstrapEmail”public string? BootstrapEmail { get; set; }Gets or sets the address of the bootstrap administrator, or null for none.
BootstrapPasswordFile
Section titled “BootstrapPasswordFile”public string? BootstrapPasswordFile { get; set; }Gets or sets the path of the file holding the bootstrap administrator’s password.
A file rather than a value, so the secret arrives as a mounted Docker/Kubernetes secret and never as an environment variable a process listing, a crash dump or a docker inspect can read.
Namespace Microsoft.Extensions.DependencyInjection
Section titled “Namespace Microsoft.Extensions.DependencyInjection”AlvoIdentityServiceCollectionExtensions
Section titled “AlvoIdentityServiceCollectionExtensions”static class
Registers Alvo’s human identity subsystem.
AddAlvoIdentity
Section titled “AddAlvoIdentity”public static IServiceCollection AddAlvoIdentity(this IServiceCollection services, Action<DbContextOptionsBuilder> configureStore, Action<AlvoIdentityOptions>? configure = null)Adds ASP.NET Core Identity’s stores, the IAlvoUserStore over them, the cookie IAlvoContextResolver (under ResolverKey), and the bootstrap administrator.
The cookie resolver is registered keyed, deliberately. The unkeyed IAlvoContextResolver is the one the Data API hands the raw API-key header to, and this one’s “presented key” is a subject ASP.NET Core already authenticated — so replacing the unkeyed registration would make a user’s uuid a working API key. A fact holds that line.
The identity store’s database must exist before any hosted service starts. The bootstrap creates the identity tables and seeds the administrator in IHostedLifecycleService.StartingAsync, which every hosted service finishes before any StartAsync begins, so that the web server, which is one, never answers a request before the users table is there. A host that provisions the database itself (a migration runner in a plain StartAsync, say) must do it before the host starts, or in a StartingAsync registered before this call; otherwise the start fails, naming what the read of the tables said.
services— The service collection to register into.configureStore— Configures the identity store’s database — the provider and its connection.configure— Configures the bootstrap administrator, if there is one.
Returns: services, for chaining.