Claims and Identity¶
When an HTTP request arrives at the API, the system needs to answer a simple question: who sent it?
This page explains how the codebase turns raw HTTP context into typed user properties that handlers and services can use. If you are new to claims-based identity in .NET, start with the key concepts glossary on the cross-cutting overview page.
The interfaces¶
Two domain interfaces define what the system needs from user identity.
ICurrentUserService¶
Answers identity questions: who is this user?
| Petfolio.Service.Domain/Common/ICurrentUserService.cs | |
|---|---|
| Property | Type | Source Claim | Purpose |
|---|---|---|---|
UserId |
string? |
NameIdentifier (sub) |
Identifies which user is making the request |
Email |
string? |
Email |
Display and communication |
IsAuthenticated |
bool |
Identity.IsAuthenticated |
Whether the request has a valid authenticated user |
Handlers inject this when they need to know who is performing an action (e.g. logging, audit trails, authorisation checks).
IClaimsPrincipalAccessor¶
Provides the raw ClaimsPrincipal to CurrentUserService. This abstraction exists so the infrastructure layer does not depend on HttpContext directly.
| Petfolio.Service.Domain/Common/IClaimsPrincipalAccessor.cs | |
|---|---|
Both interfaces live in the Domain layer (Petfolio.Service.Domain/Common/) and have no dependencies on ASP.NET Core, EF Core, or any external library.
The implementation¶
A single class, CurrentUserService, implements both ICurrentUserService and ITenantProvider. It reads claims from the ClaimsPrincipal and exposes them as typed properties.
Each property is evaluated lazily (on access, not on construction). If the request has no authenticated user, all properties return null or false. There is no exception, no fallback - just nulls.
What each property does¶
UserId- Finds theNameIdentifierclaim (the standardsubclaim in a JWT) and returns its string value. Returnsnullif the claim is missing.AccountId- Finds the customaccountIdclaim, attempts to parse it as aGuid, and returns the result. Returnsnullif the claim is missing or not a valid GUID. This property is part ofITenantProvider- see Multi-Tenancy.Email- Finds theEmailclaim and returns its string value. Returnsnullif missing.IsAuthenticated- Checks whether theClaimsPrincipalhas an authenticated identity. Returnsfalseif there is no principal or no identity.
How claims reach CurrentUserService¶
CurrentUserService never touches HttpContext directly. Instead, a chain of components passes the claims through.
sequenceDiagram
participant HTTP as HTTP Request
participant HC as HttpContext
participant HCA as HttpContextClaimsPrincipalAccessor
participant CUS as CurrentUserService
participant Handler as Command/Query Handler
HTTP->>HC: Request arrives
Note over HC: HttpContext.User exists<br/>(ClaimsPrincipal, may be empty)
HCA->>HC: Reads HttpContext.User
CUS->>HCA: Calls .User property
CUS->>CUS: Parses claims into typed properties
Handler->>CUS: Reads UserId, Email, IsAuthenticated
| Step | Component | Layer | What it does |
|---|---|---|---|
| 1 | ASP.NET Core | Framework | Creates HttpContext with a User property (a ClaimsPrincipal) |
| 2 | HttpContextClaimsPrincipalAccessor |
API | Reads HttpContext.User and exposes it as IClaimsPrincipalAccessor |
| 3 | CurrentUserService |
Infrastructure | Reads the ClaimsPrincipal and parses individual claims into typed properties |
| 4 | Handlers / Services | Application | Inject ICurrentUserService to access user identity |
HttpContextClaimsPrincipalAccessor¶
This is the bridge between ASP.NET Core's HttpContext and the domain's IClaimsPrincipalAccessor:
| Petfolio.Service.Api/Services/HttpContextClaimsPrincipalAccessor.cs | |
|---|---|
It does one thing: reads HttpContext.User and passes it through. This indirection means CurrentUserService is testable without a real HTTP context - in tests, you mock IClaimsPrincipalAccessor instead.
No JWT middleware yet
The codebase does not currently include JWT Bearer middleware. HttpContext.User exists on every ASP.NET Core request, but without authentication middleware, it will be an empty, unauthenticated ClaimsPrincipal. This means CurrentUserService properties will return null / false for all real HTTP requests until JWT validation is implemented. See Planned Features.
DI wiring¶
The interfaces are registered in two places, matching Clean Architecture layer boundaries.
API layer - Program.cs¶
| Petfolio.Service.Api/Program.cs | |
|---|---|
IHttpContextAccessor is an ASP.NET Core service, so the bridge that reads from it must be registered in the API layer. AddHttpContextAccessor() enables access to the current HttpContext from DI.
Infrastructure layer - DependencyInjection.cs¶
| Petfolio.Service.Infrastructure/DependencyInjection.cs | |
|---|---|
Both interfaces resolve to the same CurrentUserService class. They are registered as scoped, meaning a new instance is created for each HTTP request.
Two registrations, same class
Each AddScoped call creates a separate registration. The DI container creates two instances of CurrentUserService per request - one when ICurrentUserService is resolved and another when ITenantProvider is resolved. This is fine because CurrentUserService is stateless: it reads claims from the same ClaimsPrincipal each time.
Key takeaways¶
ICurrentUserServiceprovides user identity (UserId,Email,IsAuthenticated). Inject it in handlers.ITenantProviderprovides tenant context (AccountId). Used byPetfolioDbContext. See Multi-Tenancy.- Both are implemented by a single
CurrentUserServiceclass in the Infrastructure layer. CurrentUserServicereads claims from aClaimsPrincipalviaIClaimsPrincipalAccessor- it never touchesHttpContextdirectly.- All properties return
nullorfalsewhen there is no authenticated user. No exceptions, no fallbacks. - The
IClaimsPrincipalAccessorabstraction makesCurrentUserServicefully testable without HTTP infrastructure.
Related resources¶
- Auth and Multi-Tenancy Overview - cross-cutting concepts, claim contract, and planned features
- Multi-Tenancy - how
PetfolioDbContextusesITenantProviderto isolate data - Testing - how to mock and stub these interfaces in tests
- ADR-001: Authentication and Multi-Tenancy - design decisions and alternatives