Skip to content

Security Providers – Guide & Recipes

A compact, developer-friendly map to wire Security into your service. Pick a provider, drop in the code, and ship.


See also (deep dives)


Which provider should I use?

Provider Use when Where data/api lives Creates storage? Highlights
DS AccountRepository (DHI.Services.Provider.DS) Your app should call a remote Security Web API (accounts + tokens) Another DS host exposing /api/accounts & /api/tokens N/A Bearer via IAccessTokenProvider, retries (Polly), login/refresh/CRUD
PostgreSQL Security repos (DHI.Services.Provider.PostgreSQL) You want to store Security data in Postgres Your PostgreSQL DB Yes (auto-DDL + minimal seed) Separate repos for Accounts, UserGroups, RefreshTokens, PasswordHistory; PBKDF2 password validation

5-minute wiring

A) Consume a remote Security API (DS client)

using DHI.Services.Provider.DS;

// BaseUrl **must** point to /api/accounts (tokens/refresh are auto-derived)
var accounts = new AccountRepository(
  "baseUrl=https://security.example.com/api/accounts;provider=YourAccessTokenProvider;retries=5",
  logger);

// 1) Login (JWT + refresh)
var tokens = await accounts.CreateTokenAsync("jdoe", "S3cure!passw0rd", otp: null, otpAuthenticator: null);

// 2) Refresh
var renewed = await accounts.RefreshTokenAsync(tokens.RefreshToken.Token);

// 3) Validate credentials (no bearer)
var ok = await accounts.ValidatePassword("jdoe", "candidate");

// 4) Admin CRUD (requires repo’s bearer via IAccessTokenProvider)
var all = await accounts.GetAllAsync();
var one = await accounts.GetAsync("jdoe");
if (one.HasValue) { var acc = one.Value; acc.Enabled = true; await accounts.UpdateAsync(acc); }
await accounts.AddAsync(new Account("alice", "Alice") { Email = "alice@example.com", Activated = true });
await accounts.RemoveAsync("alice");

DS behavior notes

  • Auth headers: Bearer added only to /api/accounts/** calls; token/validation endpoints are no-bearer by design.
  • Retries: default 5, backoff 2^n seconds; retries 404 and 5xx > 500 (not 500 exactly).
  • Sync wrappers exist, but prefer Async in web/UI code.

B) Host your own Security data (PostgreSQL)

using DHI.Services.Provider.PostgreSQL;

// One repo per table (all auto-create tables if missing)
var accRepo  = new AccountRepository("Host=pg;Database=security;Username=svc;Password=***", logger);
var grpRepo  = new UserGroupRepository("Host=pg;Database=security;Username=svc;Password=***", logger);
var rtRepo   = new RefreshTokenRepository("Host=pg;Database=security;Username=svc;Password=***", logger);
var pwdRepo  = new PasswordHistoryRepository("Host=pg;Database=security;Username=svc;Password=***", logger);

// Seed caution: first run seeds a demo admin + default groups. Remove in real envs.
var acc = new Account("jdoe", "John Doe") { Email = "jdoe@example.com", Activated = true, Enabled = true };
acc.SetPassword("S3cure!passw0rd");  // sets PBKDF2 salt+hash
accRepo.Add(acc);

// Validate
var ok = await accRepo.ValidatePassword("jdoe", "S3cure!passw0rd");

// Groups
grpRepo.Add(new UserGroup("Operators", "Ops team", new HashSet<string>{ "jdoe" }));

// Lock/unlock helpers
var maybe = accRepo.Get("jdoe");
if (maybe.HasValue) { accRepo.LockAccount(maybe.Value, TimeSpan.FromMinutes(15)); accRepo.UnlockAccount(maybe.Value); }

// Refresh tokens
var rt = rtRepo.GetByToken("rTok123");
if (rt.HasValue) { var e = rt.Value; e.Expiration = DateTime.UtcNow.AddDays(90); rtRepo.Update(e); }

PostgreSQL behavior notes

  • Tables auto-created: accounts, usergroups, refreshtokens, passwordhistory (+ indexes).
  • Password hashing: validates legacy 20-byte SHA-1, writes/validates PBKDF2 (salt+hash, 10k iters).
  • Seeding (first run): a demo admin user + default groups (remove immediately in non-test envs).
  • Table override: each repo supports a Table= override via the underlying PostgreSQL provider infrastructure.

DS API mapping (mental model)

You call (DS repo) It does (HTTP)
CreateToken* POST {root}/api/tokens
RefreshToken* POST {root}/api/tokens/refresh
ValidatePassword* POST {root}/api/accounts/validation
Get/GetAll/Add/Update/Remove/Count REST on {root}/api/accounts

You pass BaseUrl = {root}/api/accounts; token endpoints are auto-derived.


Configuration cheatsheet

DS client connection string

  • baseUrl (required): https://…/api/accounts
  • provider (required): identifies your IAccessTokenProvider
  • retries (optional): default 5

PostgreSQL connection string

  • Standard Npgsql; each repo targets one table in public by default.
  • Optional Table=myschema.mytable per repository if you want custom names.

Security checklist (read me before prod)

  • TLS everywhere (API & DB).
  • Rotate & store secrets in a secret manager; never in code.
  • Remove seed user demo_admin_to_be_deleted and fix default groups on first deploy.
  • Least privilege DB role (CRUD on these tables only).
  • UTC timestamps (mixed timestamptz/without tz columns—normalize in app).
  • Don’t log request bodies for login/refresh endpoints.
  • Use PBKDF2 on new password writes (automatic via SetPassword).

Foot-guns & fixes

Symptom Likely cause Fix
401/403 on DS account CRUD IAccessTokenProvider issues a token without admin scope/role Issue a service-to-service token accepted by the Security API’s admin policy
DS retries keep happening on 404 Remote service still warming up/behind gateway Let retries complete; verify baseUrl → /api/accounts
Login fails with 400 Wrong password, account locked/disabled, or missing/invalid OTP Read the response; if OTP required, prompt and pass otp + otpAuthenticator
Password validates locally but fails after change Legacy SHA-1 record not upgraded Ensure users change passwords → stored as PBKDF2; validation covers both
Seed admin still present First-run seeding not cleaned Delete demo_admin_to_be_deleted and remove from groups

Quick reference

Need… Use… Notes
Call a remote Security API Provider.DS.AccountRepository baseUrl=/api/accounts; tokens/refresh auto-derived
Store security data yourself Provider.PostgreSQL repos Auto-DDL; PBKDF2; seed admin/group on empty DB
Lock/unlock accounts LockAccount/UnlockAccount/ResetAccount (PG) Helpers mutate fields then Update
Validate password ValidatePassword on either provider DS hits /validation; PG checks PBKDF2/SHA-1
Refresh JWT RefreshToken* (DS) Posts to /api/tokens/refresh

Bottom line:

  • Use DS when Security lives elsewhere and you just need a robust client (login/refresh/admin).
  • Use PostgreSQL when you own the Security store and want simple, code-first tables with solid password hashing.