Database Setup (EF Core)
The PrimusSaaS.Identity.Broker.EntityFrameworkCore package provides a production-ready EfCorePrimusUserStore<TUser, TContext> that persists users to any database supported by EF Core.
The package depends only on the EF Core abstraction layer — no specific database provider is bundled. You pick the provider that fits your stack.
Supported Databases
| Database | NuGet Provider Package |
|---|---|
| SQL Server / Azure SQL | Microsoft.EntityFrameworkCore.SqlServer |
| PostgreSQL | Npgsql.EntityFrameworkCore.PostgreSQL |
| SQLite (local dev / edge) | Microsoft.EntityFrameworkCore.Sqlite |
| MySQL / MariaDB | Pomelo.EntityFrameworkCore.MySql |
| Azure Cosmos DB | Microsoft.EntityFrameworkCore.Cosmos |
| Oracle | Oracle.EntityFrameworkCore |
Installation
# Core broker + EF Core extension
dotnet add package PrimusSaaS.Identity.Broker
dotnet add package PrimusSaaS.Identity.Broker.EntityFrameworkCore
# Plus your database provider — pick one:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer # SQL Server
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL # PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Sqlite # SQLite
dotnet add package Pomelo.EntityFrameworkCore.MySql # MySQL
Step 1 — Define Your User Entity
Inherit from PrimusBrokerUser and add any application-specific columns:
using PrimusSaaS.Identity.Broker.EntityFrameworkCore;
public class AppUser : PrimusBrokerUser
{
// Add your own columns here
public string? TenantId { get; set; }
public string? SubscriptionTier { get; set; }
}
PrimusBrokerUser already provides:
| Property | Type | Description |
|---|---|---|
Id | string | Stable identifier (GUID by default) |
Email | string | Primary lookup key — unique |
DisplayName | string? | Optional display name |
Role | string? | Single-role shorthand — kept for simple apps and backward compatibility |
Roles | ICollection<PrimusUserRole> | Multi-role navigation collection — each entry becomes a ClaimTypes.Role claim |
LastProvider | string? | Last provider used to authenticate this user (azure, auth0, google, okta, local, saml:*) |
FailedLoginCount | int | Consecutive failed logins — reset on success |
LockoutEnd | DateTimeOffset? | Null = not locked out |
CreatedAt | DateTimeOffset | First record creation |
LastLoginAt | DateTimeOffset? | Last successful login |
Step 2 — Create Your DbContext
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<AppUser> Users { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<AppUser>(e =>
{
e.HasKey(u => u.Id);
e.HasIndex(u => u.Email).IsUnique();
e.Property(u => u.Email).IsRequired().HasMaxLength(256);
});
// Required: configure the roles join table so EF migrations create it.
// Without this, role persistence breaks silently.
builder.Entity<PrimusUserRole>(e =>
{
e.HasKey(r => r.Id);
e.HasIndex(r => new { r.UserId, r.Name }).IsUnique();
e.Property(r => r.Name).IsRequired().HasMaxLength(128);
});
}
}
Step 3 — Implement Your User Store
Subclass EfCorePrimusUserStore<TUser, TContext>:
public sealed class AppUserStore : EfCorePrimusUserStore<AppUser, AppDbContext>
{
public AppUserStore(AppDbContext db) : base(db) { }
// Override to set fields when a user logs in for the first time (JIT provisioning)
protected override AppUser CreateUser(string email, string provider, ClaimsPrincipal? claims)
=> new() { Email = email, LastProvider = provider, Role = "User" };
}
The base class automatically handles JIT provisioning, lockout enforcement, and last-provider tracking. For custom claims or multi-role RBAC, see Advanced Configuration.
Step 4 — Register in Program.cs
The SQLite example below works for local development. For production, swap options.UseSqlite(...) for the provider matching your database from the Supported Databases table above.
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=myapp.db"));
builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());
builder.Services.AddPrimusEfCoreUserStore<AppUserStore>();
Step 5 — Add Configuration
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=myapp.db"
}
}
In production, set ConnectionStrings__DefaultConnection as an environment variable. Never commit real connection strings to source control.
Step 6 — Run Migrations
Migrations create the initial schema when you first set up the database, and apply any changes whenever you update your model. Run these two commands once to get started:
dotnet ef migrations add InitialCreate
dotnet ef database update
Run dotnet ef migrations add <Name> again whenever you add or change properties on AppUser or PrimusBrokerUser.
To apply migrations automatically on startup, add this to Program.cs before app.Run():
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync();
}
Available built-in implementations
| What you need | Built-in option | Notes |
|---|---|---|
| User persistence | EfCorePrimusUserStore (this page) | Any EF Core-supported database |
| Audit logging — file | FileAuditSink | JSON Lines, async writes |
| Audit logging — logger | LoggerAuditSink | Writes to ILogger / stdout |
| Audit logging — multi-sink | CompositeAuditSink | Fan-out to any combination |
| Audit logging — Redis | RedisAuditSink | Distributed, last 10 k events |
| Rate limiting — single server | InMemoryRateLimiter | Default — no external dependencies |
| Rate limiting — distributed | RedisRateLimiter | Required for multi-instance deployments |
For Redis setup, see Bot Protection.
Next Steps
- Integration Guide — complete setup walkthrough from install to first authenticated request
- Advanced Configuration — custom
IPrimusAuthUserStoreimplementations, data protection, and HA clustering - Bot Protection & Rate Limiting — Redis-backed distributed rate limiting for multi-instance deployments
- Endpoint Reference — complete endpoint documentation with request and response shapes