Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Andrespeerez/porfolio-blog/llms.txt

Use this file to discover all available pages before exploring further.

The Application layer defines three port interfaces that act as contracts between the business logic and the infrastructure adapters. By depending only on these abstractions, the use cases remain fully independent of EF Core, ASP.NET Core Identity, and cookie middleware — any of those can be swapped without touching a single line of business logic.

IUserRepository

Provides read and write access to the user store. Defined in Application/Interfaces/Repositories to signal that it is a persistence port, not a service.
Application/Interfaces/Repositories/IUserRepository.cs
public interface IUserRepository
{
    Task<User?> GetByEmailAsync(string email);
    Task AddAsync(User user);
}

Methods

GetByEmailAsync
Task<User?>
Looks up a user by their email address. Returns null when no matching record exists. Used by both AuthenticateUser (to verify credentials) and RegisterUser (to detect duplicate registrations).
AddAsync
Task
Persists a newly created User entity and saves changes to the underlying store. Called by RegisterUser after User.Create(...) builds the domain object.

Return values

GetByEmailAsync
Task<User?>
Resolves to the matching User entity, or null if the email is not registered.
AddAsync
Task
Completes once the record has been written and the EF Core change tracker has been flushed. Throws on constraint violations (e.g., duplicate email at the database level).

Infrastructure implementation

UserRepository in the Infrastructure layer wraps an EF Core DbContext backed by SQLite. It executes FirstOrDefaultAsync for lookups and calls SaveChangesAsync after every write.
The interface intentionally exposes only the two operations that the current use cases require. Additional query methods (e.g., GetByIdAsync) should be added to the interface and implementation together, driven by a new use-case need — never speculatively.

IPasswordHasher

Abstracts password hashing and verification so that the hashing algorithm can be swapped without touching any use-case code.
Application/Interfaces/Services/IPasswordHasher.cs
public interface IPasswordHasher
{
    bool Verify(string hashedPassword, string password);
    string Hash(string rawPassword);
}

Methods

Hash
string
Accepts a plain-text password and returns a hashed string suitable for storage. Called by the domain factory User.Create(...) during registration. The raw password is never stored.
Verify
bool
Compares a plain-text password candidate against a previously hashed value. Returns true when they match, false otherwise. Called by AuthenticateUser.ExecuteAsync during login.

Return values

Hash
string
A hashed representation of the raw password. The format is determined by the underlying implementation (BCrypt, PBKDF2, etc.).
Verify
bool
true when the plain-text password matches the stored hash; false when it does not.

Infrastructure implementation

IdentityPasswordHasher wraps Microsoft.AspNetCore.Identity.IPasswordHasher<User>, which uses PBKDF2 with HMAC-SHA256 by default. Swapping to BCrypt or Argon2 requires only a new implementation class registered in the DI container — no use-case changes.
IPasswordHasher.Hash must be called only at account-creation time. Never re-hash a value that is already hashed; always pass the original plain-text input.

ISessionManager

Abstracts the creation and revocation of authenticated sessions (authentication cookies). Keeps ASP.NET Core’s cookie middleware out of the Application layer.
Application/Interfaces/Services/ISessionManager.cs
public interface ISessionManager
{
    Task SignInAsync(User user);
    Task SignOutAsync();
}

Methods

SignInAsync
Task
Creates an authentication cookie for the given user. The implementation builds a ClaimsPrincipal from the user’s ID and email, then calls HttpContext.SignInAsync with the configured cookie scheme.
SignOutAsync
Task
Revokes the current user’s authentication cookie by calling HttpContext.SignOutAsync. Safe to call even when no session is active — the underlying middleware treats it as a no-op in that case.

Return values

SignInAsync
Task
Completes once the authentication cookie has been written to the HTTP response. Throws if IHttpContextAccessor.HttpContext is null (i.e., called outside of an active HTTP request).
SignOutAsync
Task
Completes once the sign-out has been processed and the cookie removed from the response.

Infrastructure implementation

CookieSessionManager uses IHttpContextAccessor to access the current HttpContext and delegates to ASP.NET Core’s built-in cookie authentication handler. Claims written during SignInAsync:
ClaimValue
ClaimTypes.NameIdentifieruser.Id.ToString()
ClaimTypes.Emailuser.Email
Because Blazor Server runs on a persistent SignalR circuit rather than discrete HTTP requests, IHttpContextAccessor is injected at startup before the circuit is established. CookieSessionManager captures the context at the moment of the initial HTTP handshake, which is the only point at which cookie auth can write response headers.

DI wiring

All three interfaces are registered in Program.cs with scoped lifetime, matching the per-request / per-circuit lifecycle of both HTTP and Blazor Server contexts.
Program.cs
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IPasswordHasher, IdentityPasswordHasher>();
builder.Services.AddScoped<ISessionManager, CookieSessionManager>();
To swap implementations — for example, to use a PostgreSQL-backed repository or a third-party authentication provider — only the infrastructure adapter needs to change. Register the new class against the same interface and the use cases will pick it up automatically. The Application project requires zero modifications.

Build docs developers (and LLMs) love