ITLab.Template.DevPack.Identity
Defines identity context contracts, provider constants and user scope helpers.
Componentes
| Tipo | Nome | Responsabilidade |
|---|---|---|
| Interface | ICurrentUser | Read and scope user context for current execution |
| Interface | ICurrentUserAccessor | Store and retrieve ambient UserInfo |
| Class | CurrentUser | Default ICurrentUser implementation |
| Class | AsyncLocalCurrentUserAccessor | Async-flow storage for current user snapshot |
| Class | UserInfo | User context data model |
| Enum | EIdentityProvider | Supported identity provider kinds |
| Class | IdentitySchemas | Authentication scheme and policy constants |
| Abstract Class | BaseIdentityUser | Base ASP.NET Core IdentityUser<int> with domain events, audit tracking, soft-delete, and activation |
| Abstract Class | BaseIdentityRole | Base ASP.NET Core IdentityRole<int> with domain events, audit tracking, activation, and multi-tenancy |
| Static Class | CurrentUserExtensions | Extension methods for ICurrentUser; provides AsSystemTenant for background operation scopes |
| Static Class | IdentityProviderName | Canonical string constants for identity provider names (DefaultProvider, MicrosoftEntraID, Ldap) |
Como usar
// Typical registration for request-scoped user context.
services.AddSingleton<ICurrentUserAccessor>(AsyncLocalCurrentUserAccessor.Instance);
services.AddScoped<ICurrentUser, CurrentUser>();
// Temporary system scope for background operations.
using (currentUser.AsSystemTenant(tenantId: 42))
{
await service.ExecuteTenantMaintenanceAsync();
}
Authentication scheme — production checklist
ASP.NET Core exige um DefaultChallengeScheme configurado para o pipeline de autorização. Sem um, qualquer request não autenticado a um endpoint RequireAuthorization() levanta InvalidOperationException → 500 (em vez de 401). Configure um dos esquemas:
JWT Bearer (mais comum em APIs)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.Authority = builder.Configuration["Auth:Authority"];
opt.Audience = builder.Configuration["Auth:Audience"];
// ...
});
Microsoft Entra ID (anteriormente Azure AD)
Ver AuthSettings/README.md — DTOs tipados (EntraIdWebApiSettings, EntraIdSpaSettings, EntraIdAgentSettings) cobrem os três fluxos canônicos.
Cookie (server-rendered)
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(...);
Pipeline order
app.UseAuthentication();
app.UseDevPackIdentityContext(); // bridge HttpContext.User → AsyncLocal (DevPack 10.3.0+)
app.UseAuthorization();
Ordem crítica: UseAuthentication popula HttpContext.User; UseDevPackIdentityContext projeta as claims em AsyncLocalCurrentUserAccessor (consumido por RoleBasedPermissionEvaluator); UseAuthorization então avalia as policies. Esquecer o middleware central → RoleBasedPermissionEvaluator recebe UserRoles = [] → 403 em tudo, mesmo com auth correto.
Claim de tenant (10.5.0)
O mapper default lê também um claim tenant (ou tid) e popula UserInfo.TenantId / ICurrentUser.TenantId. Daí o global query filter (ApplyMultiTenantQueryFilters) e o MultiTenantEntityInterceptor ativam sem mudança nos handlers. Retrocompatível: ausência ou valor não-numérico do claim → TenantId = 0 (host/global, comportamento single-tenant anterior). Para resolução por header/subdomínio em vez de claim, ver MultiTenancy/README.md.
Template scaffold note
O template ITLab.Template.Next nasce com autenticação funcional (ADR-006 do template): ASP.NET Identity local + dual-scheme — cookie BFF HttpOnly para browser e Bearer JWT assimétrico validado por JWKS para mobile/service-to-service — montados sobre os blocos desta área (BaseIdentityUser/Role, ITokenService, ISigningKeyProvider, JwksDocumentBuilder, AddDevPackDualScheme; ver § Self-issued tokens & dual scheme). Não existe mais placeholder de autenticação. Ver CLAUDE.md § Authentication do template.
Self-issued tokens & dual scheme (10.4.0)
Para apps que emitem os próprios tokens (Identity local), em vez de validar um IdP externo. Dois schemes coexistem: cookie HttpOnly para browser (BFF) e Bearer JWT assimétrico (validado por JWKS) para mobile/service-to-service. Ver ADR-005.
Componentes
| Tipo | Nome | Responsabilidade |
|---|---|---|
| Interface | Authentication.Bearer.ISigningKeyProvider | Chave de assinatura ativa + chaves públicas para JWKS |
| Class | EphemeralRsaSigningKeyProvider | Chave RSA em memória (dev) |
| Class | FileSigningKeyProvider | Chave RSA de PEM em disco (prod) |
| Interface | Authentication.Bearer.ITokenService | Emite access token assinado a partir de claims |
| Class | DefaultJwtTokenService | Implementação JWT RSA/ECDSA com kid |
| Record | Authentication.Bearer.AccessToken | JWT assinado + instante de expiração |
| Static Class | Authentication.Bearer.JwksDocumentBuilder | Constrói o JWKS (RFC 7517) das chaves públicas |
| Abstract Class | RefreshToken | Refresh token persistido (hash, rotação, revogação) |
| Interface | IRefreshTokenStore | Persistência de refresh tokens (add/find/rotate/revoke) |
| Static Class | RefreshTokenGenerator | Gera token opaco + hash de lookup |
| Abstract Class | ServiceClient | Cliente de máquina (client-credentials) com secret BCrypt |
| Options | AppSettings.IdentityTokenOptions | Issuer/Audience/lifetimes/PEM path (seção Authentication:Token) |
| Static Class | DevPackIdentityServiceCollectionExtensions | AddDevPackSigningKey / AddDevPackTokenService / AddDevPackDualScheme |
| Abstract Class | Infrastructure.EfCore.BaseIdentityDbContext<TUser,TRole> | IdentityDbContext + convenções (schema identity, sem prefixo AspNet) |
Como usar
// 1. Signing key: ephemeral em dev, PEM em prod (falha-cedo sem chave fora de dev).
builder.Services.AddDevPackSigningKey(
builder.Configuration[$"{IdentityTokenOptions.ConfigSectionPath}:SigningKeyPemPath"],
builder.Environment.IsDevelopment());
// 2. Token service (binda IdentityTokenOptions da seção "Authentication:Token").
builder.Services.AddDevPackTokenService(builder.Configuration);
// 3. Dual scheme: cookie (browser) + bearer (mobile/S2S), selecionado por request.
builder.Services
.AddAuthentication(IdentitySchemas.Smart.Name)
.AddDevPackDualScheme(ctx =>
ctx.Request.Headers.Authorization.ToString().StartsWith("Bearer ", StringComparison.Ordinal)
? JwtBearerDefaults.AuthenticationScheme
: CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { /* HttpOnly, Secure, SameSite */ })
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => { /* ValidIssuer/Audience + IssuerSigningKeys públicas */ });
// 4. Publicar o JWKS para validadores externos (service-to-service).
app.MapGet("/.well-known/jwks.json", (ISigningKeyProvider keys) =>
Results.Json(JwksDocumentBuilder.Build(keys.PublicKeys)));
Tabelas de Identity — schema dedicado
BaseIdentityDbContext<TUser,TRole> move as sete tabelas de Identity para o schema identity (configurável via IdentitySchema) e dropa o prefixo AspNet: identity.Users, identity.Roles, identity.UserRoles, identity.UserClaims, identity.UserLogins, identity.UserTokens, identity.RoleClaims. Plugue os interceptors de audit/soft-delete via AddDevPackEfCoreInterceptors() — ver Infrastructure/EfCore/README.md.
O que não fazer (self-issued tokens)
- Não usar chave simétrica quando há service-to-service — todo validador precisaria do segredo. Use assimétrica + JWKS.
- Não publicar chave privada no JWKS —
ISigningKeyProvider.PublicKeysexpõe só material público eJwksDocumentBuildernunca emited/p/q. - Não persistir refresh token em claro — só o hash (
RefreshTokenGenerator.Hash). - Não embrulhar
AddCookie/AddJwtBearer—AddDevPackDualSchemesó pluga o seletor (ADR-001).
Microsoft Entra ID connector (10.9.0)
Terceiro scheme de autenticação, opt-in por tenant, que compõe com o dual scheme acima sem substituir o
DefaultProvider (ADR-010). O DevPack ship a abstração de validação per-tenant + mapeamento de claims + a
extensão de registro do scheme (AddDevPackEntraId); o wiring, o slice de sign-in e o Graph sync ficam no
consumidor. Ver Authentication/EntraId/README.md — IEntraTenantValidator
(+ PerTenantSettingsEntraTenantValidator), IEntraClaimsMapper (+ DefaultEntraClaimsMapper),
IEntraGraphClientFactory (interface-only), EntraSettingKeys e
DevPackEntraIdServiceCollectionExtensions.AddDevPackEntraId/ExtractTenantIdFromIssuer.
O que não fazer
- Do not use
AsSystemTenantto bypass user-facing authorization paths. - Do not persist
UserInfoas long-lived session storage.
Extensão pelo produto
- Products can derive from
CurrentUserto customizeIsAvailableand tenant resolution rules. - Products can add provider-specific auth settings under
Identity/AuthSettingswhile preserving shared DTO contracts.