Identity
Overview
Identity encapsulate classes and method to manipulate ClaimsPrincipal and authentication and authorization between platform services.
Install
Dependencies:
- ASOL.Core.Identity.Contracts (>= 0.0.1.39629-dev)
- Microsoft.AspNetCore.Authentication.Certificate (>= 8.0.12)
Changelog
- 0.0.1.47819-dev
- BREAKING CHANGE:
ASOL.Core.Identitywas split into two libraries -ASOL.Core.IdentityandASOL.Core.Identity.Abstractions- Support for older .NET frameworks has been removed; only .NET 8 is now supported.
AccountIdfactory methodCreateFromCertificatehas been moved to new static classAccountIdFactory
- BREAKING CHANGE:
Dependency injection
Mandatory dependency injection
public void ConfigureServices(IServiceCollection services)
{
services.AddRuntimeContexts();
services.AddRuntimeContextScope();
}
public void Configure(IApplicationBuilder app)
{
app.UsePlatformIdentityContexts();
}
Usage IRuntimeContext on Domain
public class BookCreateCommandHandler : ICommandHandler<BookCreateCommand, string>
{
public BookCreateCommandHandler(
IRuntimeContext runtimeContext)
{
RuntimeContext = runtimeContext;
}
protected IRuntimeContext RuntimeContext { get; }
/// <inheritdoc cref="ICommandHandler{TCommand, TResult}.HandleAsync(TCommand, CancellationToken)"/>
public async Task<ExecutionResult<string>> HandleAsync(XEntityNameCreateCommand command, CancellationToken ct = default)
{
var book = new Book(
Guid.NewGuid().ToString(),
// get Runtime context current security info and get claims principal
RuntimeContext.Security.User,
command.BookName,
// get Runtime context current security info and get selected organization from tenant.
RuntimeContext.Security.SelectedOrganizationId);
await Books.AddAsync(book);
await Books.UnitOfWork.CompleteAsync(ct);
return new ExecutionResult<string>(book.Id);
}
private void SecurityContextSamples()
{
// selected language
var currentLangCode = RuntimeContext.Localization.LanguageCode;
// current traceId
var traceId = RuntimeContext.Trace.TraceId;
// test authenticated or anonymous access
if (!RuntimeContext.Security.IsAuthenticated)
{
return; // anonymous access
}
// how to get accountId / universal unique identifier for autheticated entity (user or service). It's complex identifier.
var accountId = RuntimeContext.Security.AccountId;
// how to get client (service) identifier of authenticated service.
var clientId = RuntimeContext.Security.ClientId;
if (RuntimeContext.Security.HasTenantContext)
{
// how to get current TenantId and selected mandant organization id and code.
var tenantId = RuntimeContext.Security.TenantId;
var organizationId = RuntimeContext.Security.SelectedOrganizationId;
var organizationCode = RuntimeContext.Security.SelectedOrganizationCode;
}
if (RuntimeContext.Security.HasUserContext)
{
// how to get unique identifier for authenticated user.
var userId = RuntimeContext.Security.UserId;
// how to access direct to specified authenticated claims
// how to get user email address from authenticated claims.
// optional claim value - return can be null or claims value
var userUmail = RuntimeContext.Security.User.FindClaimValue(ASOL.Core.Identity.KnownClaims.Email);
// mandatory claim - return always not null value
userUmail = RuntimeContext.Security.User.GetClaimValue(ASOL.Core.Identity.KnownClaims.Email);
// extensions method for same functionality as GetClaimValue(...)
userUmail = RuntimeContext.Security.User.GetEmail();
}
}
}
Create local authorized scope in anonymous request
Processing anonymous endpoint (REST API without standard identity, Anonymous REST API endpoints or events without tokens).
Remarks: This identity usage only under created scope and only within runtime current service. All outside communication with other services usage client credentials (ClientId and ClientSecret authentication) without artificalUser identity.
public class MyClass
{
public MyClass(
IServiceProvider serviceProvider,
IRuntimeContext runtimeContext)
{
ServiceProvider = serviceProvider;
RuntimeContext = runtimeContext;
}
protected IServiceProvider ServiceProvider { get; }
protected IRuntimeContext RuntimeContext { get; }
public async Task LocalAuthorizeForAnonymousRequestAsync(CancellationToken ct)
{
var tenantId = "XTenant"; // tenantId for example XTenant
var organizationId = "asd654-98as7d9-asd6f4"; // (optional)
var organizationCode = "12346|CZ"; // (optional)
using (var scope = ServiceProvider.GetRequiredService<IRuntimeContextScope>())
{
// Create new multitenancy context to correct access on local persistence layer.
var tenantHolder = scope.ScopeProvider.GetRequiredService<ITenantContextHolder>();
tenantHolder.TenantContext = new TenantContext { Tenant = new TenantInfo(tenantId, null) };
// This ClaimsPrincipal is only used in this service runtime scope.
// A standard service account is always used for call an external services.
var languageCode = RuntimeContext.Localization?.LanguageCode ?? ClaimsPrincipalExtensions.DefaultLanguageCode;
var artificalUser = ClaimsPrincipalBuilder.CreateClientPrincipal(scope.SsoAuthOptions.ClientId, tenantId)
.AddLocale(languageCode)
.AddSelectedOrganization(organizationId, organizationCode) // (optional)
.AddAuthenticateType()
.Build();
// setup scope context with local authenticated artificalUser.
scope.LocalImpersonate(artificalUser, RuntimeContext.Trace);
// Example to create and run your code under artificalUser authentication and usage Tenant scope for TenantId.
// var myCodeInstance = scope.ScopeProvider.GetRequiredService<MyCode>();
// await myCodeInstance.RunAsync(ct);
}
}
}
General impersonate (switch context to other identity)
Requirements:
- nuget ASOL.Core.Identity version 0.0.1.19303-dev or newest.
- nuget ASOL.IdentityManager.Extensions version 0.0.1.19303-dev or newest.
- Setup tenant federation data (Subject manager domain, Organization relationships).
- Authorized request. Anonymous requests cannot be impersonated or use tenant federation.
public class MyClass
{
public MyClass(
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
protected IServiceProvider ServiceProvider { get; }
public async Task ImpersonateRequestAsync(CancellationToken ct)
{
var tenantId = "XTenant"; // target tenantId
var organizationId = "asd654-98as7d9-asd6f4"; // (optional) target organizationId
var organizationCode = "12346|CZ"; // (optional) target organizationCode
using (var scope = ServiceProvider.GetRequiredService<IRuntimeContextScope>())
{
// Create new multitenancy context to correct access on local persistence layer.
var tenantHolder = scope.ScopeProvider.GetRequiredService<ITenantContextHolder>();
tenantHolder.TenantContext = new TenantContext { Tenant = new TenantInfo(tenantId, null) };
var requestedIdentity = ClaimsPrincipalBuilder.CreateClientPrincipal(scope.SsoAuthOptions.ClientId, tenantId)
.AddLocale(languageCode)
.AddSelectedOrganization(organizationId, organizationCode) // (optional)
.AddAuthenticateType()
.Build();
// impersonation with federate current identity to target tenant and organization under target tenant.
var impersonatedIdentity = await scope.ImpersonateAsync(requestedIdentity, ct);
// from this point all local runtime and all calls to outside this domain use this verified impersonatedIdentity
// Example call other service with
// this call always usage impersonatedIdentity
//var otherServiceClient = scope.ScopeProvider.GetRequiredService<IOtherServiceClient>();
//var response = await otherServiceClient.SomeRequestAsync();
}
}
}
Impersonate with Tenant federation (switch context to federated tenant)
Requirements:
- nuget ASOL.Core.Identity version 0.0.1.19000-dev or newest.
- nuget ASOL.IdentityManager.Extensions version 0.0.1.19000-dev or newest.
- Setup tenant federation data (Subject manager domain, Organization relationships).
- Authorized request with selected Tenant and Organization. Anonymous requests cannot be impersonated or use tenant federation.
public class MyClass
{
public MyClass(
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
protected IServiceProvider ServiceProvider { get; }
public async Task FederateRequestAsync(CancellationToken ct)
{
var tenantId = "XTenant"; // target tenantId
var organizationId = "asd654-98as7d9-asd6f4"; // (required) target organizationId
var organizationCode = "12346|CZ"; // (required) target organizationCode
using (var scope = ServiceProvider.GetRequiredService<IRuntimeContextScope>())
{
// Create new multi-tenancy context to correct access on local persistence layer.
var tenantHolder = scope.ScopeProvider.GetRequiredService<ITenantContextHolder>();
tenantHolder.TenantContext = new TenantContext { Tenant = new TenantInfo(tenantId, null) };
// impersonation with federate current identity to target tenant and organization under target tenant.
var federatedIdentity = await scope.FederateAsync(tenantId, organizationId, organizationCode, ct);
// from this point all local runtime and all calls to outside this domain use this verified federatedIdentity
// Example call other service with
// this call always usage federated (impersonation) context with federatedIdentity
//var otherServiceClient = scope.ScopeProvider.GetRequiredService<IOtherServiceClient>();
//var response = await otherServiceClient.SomeRequestAsync();
}
}
}
Create MasterRuntimeContext in a new service scope
When creating a new service scope that needs to cooperate with the master runtime context (e.g. MasterDbContext), you need to create a new master runtime context using the factory.
using ASOL.Core.Identity;
using ASOL.Core.Identity.Contexts.Factories;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// ...
using var scope = Provider.CreateScope();
var masterRuntimeContextHolder = scope.ServiceProvider.GetRequiredService<IScopeContextHolder<IMasterRuntimeContext>>();
var masterRuntimeContextFactory = scope.ServiceProvider.GetRequiredService<IMasterRuntimeContextFactory>();
masterRuntimeContextHolder.Context = masterRuntimeContextFactory.Create();
// ...
AccountId
AccountId encapsulate general identifier both User and Service accounts. AccountId have methods a properties to parse or compile AccountId identifier on any direction.
AccountId for current context
You needs IRuntimeContext from DI
runtimeContext.Security.AccountId
AccountId from ClaimsPrincipal
- use static method
AccountId.CreateFromPrincipal(myClaimsPrincipal)
Transform ClientId as AccountId
- ClientId can be transform into AccountId with static method CreateFromService(clientId)
AccountId.CreateFromService("12346-465987-87654-434")
Transform UserName as AccountId
- UserName can be transform into AccountId with static method CreateFromUserName(userName)
AccountId.CreateFromUserName("my@example.com")