Optimistic Concurrency
Mutating endpoints in the Management API v2 and User Store API v2 that expose a versioned resource use HTTP optimistic concurrency control as defined by RFC 9110 §13.
The contract follows the standard ETag / If-Match exchange — no proprietary extensions — so any RFC 9110-compliant HTTP client can participate without a ProAuth-specific SDK.
Where this applies
Concurrency control is enforced for every resource whose DTO derives from ConcurrencyAwareDto. In v2 this includes (non-exhaustive): Tenant, ClientApp, IdpInstance, User, Group, ProAuthUser, ProAuthRole, ProAuthRoleAssignment, ClaimRule, Subscription, CustomView, and most other long-lived configuration entities. v1 endpoints do not enforce concurrency.
At a glance
| HTTP method | Server emits ETag? | Client must send If-Match? | Conflict response |
|---|---|---|---|
GET | Yes (on aware resources) | No | n/a |
POST (create) | Yes — ETag of the newly created resource | No | n/a |
PUT (replace) | Yes — new ETag of the updated resource | Yes | 412 Precondition Failed |
PATCH | Yes — new ETag of the updated resource | Yes | 412 Precondition Failed |
DELETE | n/a | Yes | 412 Precondition Failed |
Missing the If-Match header on a write to an aware resource yields 428 Precondition Required. A malformed or syntactically inconsistent If-Match value yields 400 Bad Request.
ETag format
ProAuth emits strong, double-quoted ETags whose tag value is the lowercase 32-character hexadecimal form (the .NET Guid "N" format) of the entity's internal concurrency token:
ETag: "8b1f3c2d4e5f6789a0b1c2d3e4f50617"Weak ETags (W/"…") are never emitted by ProAuth and must not be sent on If-Match.
Read–then–write workflow
GETthe resource and capture theETagresponse header.- Mutate the local copy.
- Send the mutation back with
If-Matchset to the captured ETag. - On
200/201/204, capture the newETagfrom the response and use it for the next mutation. - On
412 Precondition Failed, refetch the resource and reconcile.
GET /api/management/v2/Tenant/3a7e1f5a-1b2c-4d5e-9f80-aa11bb22cc33 HTTP/1.1
Authorization: Bearer …
HTTP/1.1 200 OK
ETag: "8b1f3c2d4e5f6789a0b1c2d3e4f50617"
Content-Type: application/json
{ "id": "3a7e1f5a-…", "name": "Acme", … }PUT /api/management/v2/Tenant/3a7e1f5a-1b2c-4d5e-9f80-aa11bb22cc33 HTTP/1.1
Authorization: Bearer …
If-Match: "8b1f3c2d4e5f6789a0b1c2d3e4f50617"
Content-Type: application/json
{ "id": "3a7e1f5a-…", "name": "Acme Holdings", "subscriptionId": "…" }A successful response carries the new token to use for the next write:
HTTP/1.1 200 OK
ETag: "f60a3b8c5d2e1f9087564738291a0b1c"Error responses
412 Precondition Failed — Conflict
The request reached the server but its If-Match does not match the current server-side token. Another client has updated the resource since the token was issued. Refetch, reconcile, and retry.
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.13",
"title": "Precondition Failed",
"status": 412,
"detail": "The supplied If-Match precondition does not match the current resource version."
}428 Precondition Required — Missing If-Match
The endpoint requires If-Match and none was supplied.
HTTP/1.1 428 Precondition Required
Content-Type: application/problem+json
{
"type": "https://tools.ietf.org/html/rfc6585#section-3",
"title": "Precondition Required",
"status": 428,
"detail": "If-Match is required for this operation."
}400 Bad Request — Malformed precondition
Sent when:
If-Matchis present but not a syntactically valid entity-tag,- the request body's embedded concurrency token contradicts the
If-Matchheader (for DTOs that carry their ownConcurrencyTokenfield).
Body-level concurrency token
Update*Command and Patch*Command DTOs derive from ConcurrencyAwareDto, which exposes an optional ConcurrencyToken property. When present the value must equal the If-Match header tag; the server uses the header as the authoritative precondition and treats body/header divergence as a malformed request (400 Bad Request).
Most clients should rely solely on the If-Match header and leave the body's ConcurrencyToken unset.
OpenAPI surface
Concurrency-aware operations are advertised in the OpenAPI document:
- Every concurrency-protected
PUT,PATCH, andDELETEdeclares anIf-Matchheader parameter as required. - Every
GET,POST,PUT, andPATCHof an aware resource declares anETagresponse header.
Generated SDKs are expected to surface the header on the operation signature or to forward it transparently.
Client SDK behaviour
The first-party .NET clients (ProAuth.Clients.Management.V2 and ProAuth.Clients.UserStore.V2) currently expose the If-Match parameter on each mutating operation. Test fixtures used by ProAuth's own system tests ship a DelegatingHandler (EtagIfMatchForwardingHandler) that captures the ETag from each successful response and replays it as If-Match on the next mutation of the same resource — a useful reference implementation for production SDKs.
v1 compatibility
The v1 endpoints (/api/management/v1/…, /api/userstore/v1/…) do not participate in optimistic concurrency. Last-writer-wins semantics still apply there; migrate to v2 to gain conflict detection.