DHI.Services.DS for Security — Internal Developer Guide¶
This provider lets your service call a remote Domain Services–based Security Web API (the same API described in our Security guide) without taking a dependency on the web layer itself. You use it when your app needs to authenticate users (JWT), refresh tokens, or manage accounts stored in a separate Security service.
Note
Some primitives (connection string format, IAccessTokenProvider, helpers) live in DS Core — this doc references those.
What you get¶
AccountRepository (DS provider)¶
A concrete client for the remote Accounts & Tokens endpoints:
- Auth flows (no bearer required):
CreateToken[Async](id, password, otp?, otpAuthenticator?)→TokenResponseRefreshToken[Async](refreshToken)→TokenResponseValidatePassword(id, password)→bool(calls/api/accounts/validation)
- Accounts (bearer required via
IAccessTokenProvider):Get[Async](id)→Maybe<Account>GetAll[Async]()→IEnumerable<Account>Add[Async](Account)/Update[Async](Account)/Remove[Async](id)Count()→int- Convenience helpers that update lockout fields then
Update:LockAccount,ResetAccount,UnlockAccount - Utility finders (client-side filter):
GetByToken[Async](token),GetByEmail[Async](email)
Under the hood the repository automatically discovers the related Tokens endpoints from the Accounts base URL:
BaseUrl (you pass): https://security.example.com/api/accounts
Tokens URL (derived): https://security.example.com/api/tokens
Refresh URL (derived): https://security.example.com/api/tokens/refresh
Validation URL (derived): https://security.example.com/api/accounts/validation
The Accounts calls send a Bearer token obtained from your
IAccessTokenProvider. The Tokens and Validation calls do not send a bearer (by design).
Instantiating the repository¶
You can build it either from a DS connection string or explicitly with baseUrl + token provider.
A) Using a DS connection string (recommended)¶
// See DS Core for the exact format and providers supported
var repo = new DHI.Services.Provider.DS.AccountRepository(
"baseUrl=https://security.example.com/api/accounts;provider=yourProvider;retries=5",
logger);
The connection string is parsed by DS Core to produce:
BaseUrl(must point to/api/accounts)- An
IAccessTokenProvider(used for all bearer-protected calls) - Optional
retryCount
B) Using DI with explicit pieces¶
var repo = new DHI.Services.Provider.DS.AccountRepository(
baseUrl: "https://security.example.com/api/accounts",
accessTokenProvider: myAccessTokenProvider, // implements IAccessTokenProvider (DS Core)
retryCount: 5,
logger: logger);
IAccessTokenProvider (from DS Core) must return a service-to-service access token usable by the Security Web API for admin/account endpoints.
Quick start: typical flows¶
1) User login → get JWT + refresh¶
var tokens = await repo.CreateTokenAsync(
id: "jdoe",
password: "S3cure!passw0rd",
otp: "123456", // optional, only if required
otpAuthenticator: "GoogleOtpAuthenticator");
var access = tokens.AccessToken.Token;
var refresh = tokens.RefreshToken.Token;
2) Refresh an expired access token¶
var tokens = await repo.RefreshTokenAsync(refreshToken);
3) Validate credentials (e.g., to preflight UI)¶
var ok = await repo.ValidatePassword("jdoe", "candidatePassword");
4) CRUD on accounts (requires repo’s bearer)¶
var all = await repo.GetAllAsync();
var maybe = await repo.GetAsync("jdoe");
if (maybe.HasValue)
{
var acc = maybe.Value;
acc.Enabled = true;
await repo.UpdateAsync(acc);
}
// create
await repo.AddAsync(new Account("alice", "Alice")
{
Email = "alice@example.com",
// SetPassword(...) is done server-side in Security WebApi for DTO paths;
// here you usually manipulate server-projected Account objects.
});
// delete
await repo.RemoveAsync("alice");
5) Lock/unlock helpers (update is performed remotely)¶
repo.LockAccount(account, TimeSpan.FromMinutes(15));
repo.ResetAccount(account, resetValue: 0);
repo.UnlockAccount(account);
Behavior & conventions¶
URLs & auth headers¶
BuildHttpRequestAsyncattachesAuthorization: Bearer <token>from yourIAccessTokenProvideronly for the account endpoints.- Token/validation endpoints are called without bearer (they use user credentials / refresh token).
Serialization¶
System.Text.Jsonwith camelCase naming and enum-as-string.- Matches the Security Web API contracts (
TokenResponse,Account, etc.).
Retry policy (Polly)¶
- Default retries: 5 (configurable).
- Backoff: 2^attempt seconds.
- Will retry for:
404 NotFound(service may be temporarily offline or warming up)- 5xx > 500 (e.g., 502/503/504, etc.)
- Will NOT retry for:
- Success (2xx), 400, 401–499 except 404 (client errors), and 500 specifically.
- After the final attempt, non-success responses are surfaced as
HttpRequestExceptionwith the server’s message when available.
Note
HTTP 500 is not retried due to the > 500 check; other 5xx are retried. This is intentional to avoid loops on generic 500s.
Synchronous wrappers¶
All public methods have synchronous counterparts that internally use AsyncHelpers.RunSync(...). Prefer the Async variants in ASP.NET and UI apps; the sync wrappers are safe for background / console usage.
Logging¶
- Warnings are logged on retry (
ILoggeroptional). - When a response is unsuccessful and has a body, the message is logged before throwing.
Maybe<T>¶
Get*methods returnMaybe.Empty<T>()for 404 NotFound. Check.HasValuebefore using.Value.
How the DS provider maps to Security Web API¶
| DS Provider method | HTTP call (remote) |
|---|---|
CreateToken[Async] |
POST {root}/api/tokens |
RefreshToken[Async] |
POST {root}/api/tokens/refresh |
ValidatePassword* |
POST {root}/api/accounts/validation |
GetAll[Async] |
GET {root}/api/accounts |
Get[Async](id) |
GET {root}/api/accounts/{id} |
Add[Async](Account) |
POST {root}/api/accounts |
Update[Async](Account) |
PUT {root}/api/accounts |
Remove[Async](id) |
DELETE {root}/api/accounts/{id} |
Count() |
GET {root}/api/accounts/count |
{root}is the Security service base (e.g.,https://security.example.com). You passBaseUrl = {root}/api/accountsto the repository; it derives the tokens/validation URLs automatically.
Integration notes & safeguards¶
- Bearer source: Ensure your
IAccessTokenProviderissues a token accepted by the Security API for admin operations (e.g., a service principal or a technical user in the “Administrators” group). - TLS only: Always call the Security Web API over HTTPS.
- Avoid leaking credentials: The provider’s auth methods post credentials in request bodies (not in query strings). Do not log request bodies in production.
- Time-outs: If you supply your own
HttpClient, configure sensible timeouts. The static client uses defaults. - Compatibility: The provider assumes the Security Web API contracts documented in the Security guide (password policy, OTP challenge format, token shapes).
Types (for reference)¶
sealed class ValidationDTO {
string Id; string Password; string Otp; string OtpAuthenticator;
}
sealed class TokenDTO {
string Token; DateTime Expiration;
}
sealed class TokenResponse {
TokenDTO AccessToken; string TokenType; TokenDTO RefreshToken;
}
The Account type is the Domain Services account model (same as used by Security). See Security guide for fields and DS Core for common primitives.
Troubleshooting¶
- 401/403 on account calls: Your
IAccessTokenProviderisn’t returning a token with sufficient privilege (check “AdministratorsOnly” policy on the server). - 400 on
CreateToken: Invalid credentials, locked or disabled account, or OTP required/invalid. If OTP is required and missing, the API returns an OTP configuration object—forward that to the client and retry withotp. - 404 with retries: The provider will retry a few times; if the service is starting up behind a gateway, this is expected briefly.
- Deadlocks in UI/ASP.NET: Use the
Asyncmethods; the sync wrappers are for non-async contexts.