Passport
Claims to User Context
Map JWT claims from a validated Passport token into the BizFirstGO IDInfo struct — the unified identity context used throughout Go.Essentials middleware and execution nodes.
The IDInfo Struct
IDInfo is the canonical identity representation in BizFirstGO. It is populated from validated JWT claims and propagated through the request pipeline. Every executor node, service, and middleware that needs identity context receives an IDInfo.
namespace BizFirst.Essentials.Identity;
/// <summary>
/// Canonical identity context — populated from a validated Passport JWT.
/// Immutable after creation — safe to share across threads.
/// </summary>
public sealed record IDInfo
{
public required string UserId { get; init; } // from "sub" claim
public required string TenantId { get; init; } // from "tenant_id" claim
public required string Email { get; init; } // from "email" claim
public string DisplayName { get; init; } = string.Empty;
public IReadOnlyList<string> Roles { get; init; } = [];
public IReadOnlyList<string> Permissions { get; init; } = [];
public bool IsServiceAccount { get; init; } // true for managed identities
public string? ManagedIdentityId { get; init; } // set for managed identity tokens
public DateTimeOffset TokenIssuedAt { get; init; }
public DateTimeOffset TokenExpiresAt{ get; init; }
}
Claim-to-IDInfo Mapping
| JWT Claim | IDInfo Property | Notes |
|---|---|---|
sub | UserId | Stable user GUID — never changes even if email changes |
tenant_id | TenantId | Required for multi-tenant data isolation |
email | Email | From email scope |
name | DisplayName | From profile scope |
roles | Roles | Array claim from roles scope |
permissions | Permissions | Optional — explicit permission strings |
iat | TokenIssuedAt | Unix timestamp → DateTimeOffset |
exp | TokenExpiresAt | Unix timestamp → DateTimeOffset |
managed_identity_id | ManagedIdentityId | Present only for managed identity tokens |
IPassportIdentityResolver
public interface IPassportIdentityResolver
{
/// <summary>
/// Builds an IDInfo from a validated ClaimsPrincipal.
/// The ClaimsPrincipal must come from a validated token — do not pass unvalidated principals.
/// </summary>
Task<IDInfo?> ResolveAsync(
ClaimsPrincipal principal,
CancellationToken ct = default);
}
// Default implementation
internal sealed class PassportIdentityResolver : IPassportIdentityResolver
{
public Task<IDInfo?> ResolveAsync(ClaimsPrincipal principal, CancellationToken ct)
{
var userId = principal.FindFirstValue("sub");
var tenantId = principal.FindFirstValue("tenant_id");
var email = principal.FindFirstValue("email");
if (userId is null || tenantId is null || email is null)
return Task.FromResult<IDInfo?>(null);
var roles = principal.FindAll("roles")
.Select(c => c.Value)
.ToList();
var permissions = principal.FindAll("permissions")
.Select(c => c.Value)
.ToList();
var isManagedIdentity = principal.HasClaim("managed_identity_id", c => c.Value is not null);
var idInfo = new IDInfo
{
UserId = userId,
TenantId = tenantId,
Email = email,
DisplayName = principal.FindFirstValue("name") ?? email,
Roles = roles,
Permissions = permissions,
IsServiceAccount = isManagedIdentity,
ManagedIdentityId= principal.FindFirstValue("managed_identity_id"),
TokenIssuedAt = DateTimeOffset.FromUnixTimeSeconds(
long.Parse(principal.FindFirstValue("iat") ?? "0")),
TokenExpiresAt = DateTimeOffset.FromUnixTimeSeconds(
long.Parse(principal.FindFirstValue("exp") ?? "0"))
};
return Task.FromResult<IDInfo?>(idInfo);
}
}
Accessing IDInfo in Request Pipeline
// In ASP.NET Core controllers — IDInfo is injected after AddPassportAuthentication()
[Authorize]
public class WorkflowController : ControllerBase
{
private readonly IPassportIdentityResolver _resolver;
[HttpPost("workflows/{id}/execute")]
public async Task<IActionResult> Execute(Guid id)
{
// Resolve IDInfo from the validated ClaimsPrincipal
var idInfo = await _resolver.ResolveAsync(User);
if (idInfo is null) return Unauthorized();
// Use IDInfo in downstream services
await _workflowService.ExecuteAsync(id, idInfo);
return Accepted();
}
}
// In Go.Essentials middleware — IDInfo is available via HttpContext extension
var idInfo = HttpContext.GetIDInfo(); // returns null if not authenticated
Role-Based Access with IDInfo
// Check role membership
if (!idInfo.Roles.Contains("admin") && !idInfo.Roles.Contains("manager"))
return Forbid();
// ASP.NET Core attribute-based authorization
[Authorize(Roles = "admin,manager")]
public IActionResult AdminAction() { ... }
// Policy-based (using Passport IAM)
builder.Services.AddAuthorization(opts =>
{
opts.AddPolicy("CanExecuteWorkflows", policy =>
policy.RequireClaim("roles", "manager", "admin", "workflow-executor"));
});
[Authorize(Policy = "CanExecuteWorkflows")]
public IActionResult ExecuteWorkflow() { ... }