Skip to content

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?)TokenResponse
    • RefreshToken[Async](refreshToken)TokenResponse
    • ValidatePassword(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.

// 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

  • BuildHttpRequestAsync attaches Authorization: Bearer <token> from your IAccessTokenProvider only for the account endpoints.
  • Token/validation endpoints are called without bearer (they use user credentials / refresh token).

Serialization

  • System.Text.Json with 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 HttpRequestException with 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 (ILogger optional).
  • When a response is unsuccessful and has a body, the message is logged before throwing.

Maybe<T>

  • Get* methods return Maybe.Empty<T>() for 404 NotFound. Check .HasValue before 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 pass BaseUrl = {root}/api/accounts to the repository; it derives the tokens/validation URLs automatically.


Integration notes & safeguards

  • Bearer source: Ensure your IAccessTokenProvider issues 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 IAccessTokenProvider isn’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 with otp.
  • 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 Async methods; the sync wrappers are for non-async contexts.