Username/Password Authentication (UserPass)
UserPass authentication provides credential-based authentication using JWT (JSON Web Tokens). Users authenticate with an identifier (e.g., email, username) and a password. On success, the system returns a pair of JWT tokens (access + refresh). Subsequent requests are authenticated by passing the access token in the standard Authorization: Bearer <token> header.
How It Works
- The caller sends credentials (identifier + password) to your login endpoint.
- Your endpoint calls
ILSCoreAuthUserPassManager<TEntityIdentifier>.Authenticate(). - The manager looks up the user via
ILSCoreAuthUserPassIdentityEntityRepository, verifies the BCrypt-hashed password, generates JWT tokens, and stores the refresh token. - The caller receives an
LSCoreJwtcontainingAccessTokenandRefreshToken. - On subsequent requests, the caller includes
Authorization: Bearer <AccessToken>. - ASP.NET Core’s built-in JWT Bearer authentication validates the token.
- The
LSCoreAuthUserPassMiddlewarereads the validated JWT claims and populatesLSCoreAuthContextEntity<TEntityIdentifier>with the user’s identifier and entity type.
Configuration
LSCoreAuthUserPassConfiguration
Extends LSCoreAuthConfiguration with JWT-specific settings:
| Property | Type | Default | Description |
|---|---|---|---|
SecurityKey | string | required | The symmetric key used to sign and validate JWTs. Must be long enough for HMAC-SHA256. |
Issuer | string | required | The JWT issuer claim (iss). Typically your application name or URL. |
Audience | string | required | The JWT audience claim (aud). Typically your API URL or client identifier. |
AccessTokenExpirationMinutes | int | 30 | How long access tokens are valid, in minutes. |
RefreshTokenExpirationDays | int | 7 | How long refresh tokens are valid, in days. |
TokenSkew | TimeSpan | 5 minutes | Clock skew tolerance for token validation. |
AuthAll | bool | false | When true, every request requires authentication. When false, only endpoints with [LSCoreAuthAttribute] are checked. |
BreakOnFailedAuth | bool | true | When true, unauthenticated requests to protected endpoints throw LSCoreUnauthenticatedException. |
Implementing the User Entity
Your user entity must implement ILSCoreAuthUserPassEntity<TEntityIdentifier>:
public interface ILSCoreAuthUserPassEntity<out TEntityIdentifier>
: ILSCoreAuthEntity<TEntityIdentifier>
{
public string? RefreshToken { get; set; }
public string Password { get; set; }
}
Example
using LSCore.Auth.UserPass.Contracts;
public class User : ILSCoreAuthUserPassEntity<Guid>
{
public Guid Id { get; set; }
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string? RefreshToken { get; set; }
// ILSCoreAuthEntity<Guid>
public Guid Identifier => Id;
}
Implementing the Repository
Your repository must implement ILSCoreAuthUserPassIdentityEntityRepository<TEntityIdentifier>:
public interface ILSCoreAuthUserPassIdentityEntityRepository<TEntityIdentifier>
{
ILSCoreAuthUserPassEntity<TEntityIdentifier>? GetOrDefault(TEntityIdentifier identifier);
void SetRefreshToken(TEntityIdentifier entityIdentifier, string refreshToken);
}
Example
using LSCore.Auth.UserPass.Contracts;
public class UserRepository : ILSCoreAuthUserPassIdentityEntityRepository<Guid>
{
private readonly AppDbContext _db;
public UserRepository(AppDbContext db)
{
_db = db;
}
public ILSCoreAuthUserPassEntity<Guid>? GetOrDefault(Guid identifier)
{
return _db.Users.FirstOrDefault(u => u.Id == identifier);
}
public void SetRefreshToken(Guid entityIdentifier, string refreshToken)
{
var user = _db.Users.First(u => u.Id == entityIdentifier);
user.RefreshToken = refreshToken;
_db.SaveChanges();
}
}
JWT Handling
Token Structure
Each JWT contains the following claims:
| Claim | Description |
|---|---|
sub | The entity identifier (e.g., user ID) |
jti | A unique token identifier (GUID) |
ls:identifier | The entity identifier (custom claim used by LSCore to populate the auth context) |
Tokens are signed with HMAC-SHA256 using the SecurityKey from configuration.
Password Hashing
Passwords are hashed using BCrypt with Enhanced hashing and a random work factor between 8 and 12. Use the provided helper to hash passwords before storing them:
using LSCore.Auth.UserPass.Domain;
var hashedPassword = LSCoreAuthUserPassHelpers.HashPassword("raw-password");
Auth Context
After middleware execution, the authenticated user’s identity is available through LSCoreAuthContextEntity<TEntityIdentifier>, which is registered as a scoped service:
public class LSCoreAuthContextEntity<TEntityIdentifier>
{
public bool IsAuthenticated => Type != null && Identifier != null;
public bool NotAuthenticated => !IsAuthenticated;
public LSCoreAuthEntityType? Type { get; set; }
public TEntityIdentifier? Identifier { get; set; }
}
For JWT-authenticated users, Type is set to LSCoreAuthEntityType.User.
DI Registration
Register UserPass auth in Program.cs:
builder.AddLSCoreAuthUserPass<
Guid, // TEntityIdentifier -- your entity's ID type
AuthManager, // TAuthPasswordManager -- your manager (or use the built-in one)
UserRepository // TLSCoreIdentityRepository -- your repository
>(new LSCoreAuthUserPassConfiguration
{
SecurityKey = "your-256-bit-secret-key-here-min-32-chars",
Issuer = "MyApp",
Audience = "MyAppUsers",
AccessTokenExpirationMinutes = 60,
RefreshTokenExpirationDays = 30
});
Using the Built-In Manager
If the default authentication flow (BCrypt verification, JWT generation) is sufficient, use the built-in LSCoreAuthUserPassManager<TEntityIdentifier>:
using LSCore.Auth.UserPass.Domain;
builder.AddLSCoreAuthUserPass<
Guid,
LSCoreAuthUserPassManager<Guid>,
UserRepository
>(new LSCoreAuthUserPassConfiguration
{
SecurityKey = "your-256-bit-secret-key-here-min-32-chars",
Issuer = "MyApp",
Audience = "MyAppUsers"
});
Middleware Setup
var app = builder.Build();
// UserPass middleware (adds ASP.NET Authentication + custom context population)
app.UseLSCoreAuthUserPass<Guid>();
app.Run();
The UseLSCoreAuthUserPass<TEntityIdentifier>() call registers two middlewares in sequence:
app.UseAuthentication()– ASP.NET Core’s built-in JWT Bearer validationLSCoreAuthUserPassMiddleware<TEntityIdentifier>– Reads JWT claims and populatesLSCoreAuthContextEntity
Complete Example
Program.cs
using LSCore.Auth.Contracts;
using LSCore.Auth.UserPass.Contracts;
using LSCore.Auth.UserPass.DependencyInjection;
using LSCore.Auth.UserPass.Domain;
var builder = WebApplication.CreateBuilder(args);
builder.AddLSCoreAuthUserPass<
Guid,
LSCoreAuthUserPassManager<Guid>,
UserRepository
>(new LSCoreAuthUserPassConfiguration
{
SecurityKey = "a-very-long-secret-key-at-least-32-characters",
Issuer = "MyApplication",
Audience = "MyApplicationUsers",
AccessTokenExpirationMinutes = 60,
RefreshTokenExpirationDays = 14
});
var app = builder.Build();
app.UseLSCoreAuthUserPass<Guid>();
// Login endpoint (public)
app.MapPost("/login", (
LoginRequest request,
ILSCoreAuthUserPassManager<Guid> authManager
) =>
{
var jwt = authManager.Authenticate(request.UserId, request.Password);
return Results.Ok(jwt);
});
// Protected endpoint
app.MapGet("/me", [LSCoreAuthAttribute] (
LSCoreAuthContextEntity<Guid> authContext
) =>
{
return Results.Ok(new { UserId = authContext.Identifier });
});
app.Run();
record LoginRequest(Guid UserId, string Password);
Registering a User (Password Hashing)
using LSCore.Auth.UserPass.Domain;
var user = new User
{
Id = Guid.NewGuid(),
Email = "user@example.com",
Password = LSCoreAuthUserPassHelpers.HashPassword("my-secure-password")
};
dbContext.Users.Add(user);
dbContext.SaveChanges();