What the product collects, stores and keeps — the inventory, the collection defaults, the
retention periods and what leaves your perimeter — is on one page of its own:
Security and data handling. This checklist is about the decisions you
make; that page is about the facts you make them on.
Issuer and certificates
- Stable HTTPS issuer, current JWKS, a planned key replacement procedure.
iss claim and in discovery.
Clients must trust keys via discovery / JWKS, never pinned static keys: then replacing a key
needs no change on their side. Outside Development the signing and encryption certificates are
required in both delivery modes, embedded and self-hosted — without them the host does not start
(keys: configuration reference).
Client secrets and storage
-
client_secretand channel tokens — out of the repo, via the environment, encrypted and rotated.
client_secret, bot tokens, channel API keys) are passed only through
environment variables / a secret store — not config files, not git. Encrypt at rest and rotate.
OIDC PKCE and state
- PKCE (
S256) required for public clients; validatestateandnonce.
S256 method is mandatory for public clients. state guards against CSRF on the
redirect, nonce binds the id_token to the session. Veriqa additionally checks a one-time
browser nonce in the callback — do not disable these checks.
Redirect URIs
- Exact-match allowlist; no wildcards in production;
httpsonly (except loopback in dev).
http only for loopback
addresses (localhost, 127.0.0.1, [::1]) during development.
Scopes and claims
- Minimal scope set;
openidrequired; do not request unnecessary PII.
openid is required, the rest as needed. Do not request email unless
needed; do not store PII beyond necessity. /userinfo returns only the claims permitted by
scopes and configuration.
Rate limiting and anti-abuse
- Core limits sized for the traffic of each route; a per-IP limiter on the reverse proxy / WAF.
authorize,
token, the callback, polling, webhooks, SignalR, the Email start page, the confirmation pages
(defaults in configuration). On top of that it counts
sign-in attempts per user (channel identity), so a confirmation cannot be brute-forced, and it
gives each relying party its own budget on the server-to-server confirmation entry point. Size the
shared buckets for the total traffic of the route: they protect availability, not one visitor from
another.
What the core does not do is key any limit by client IP or block addresses. A
per-IP limit catches the one thing the other two keys cannot see — a horizontal spray, many
accounts tried from one address, each staying under its own per-user limit — and that layer belongs
at the edge you already run: limit_req on NGINX, a rate rule on your WAF, fail2ban on the 429s
in the access log. The same deployment step configures ForwardedHeaders:KnownProxies (see
self-hosted, step 6): the edge sees the
real client address, the core only trusts what the edge forwards. An installation without an edge
limiter is open to the spray, so put one in place before going live.
Refresh token rotation
- Keep refresh-token rotation and revocation on.
EnableRefreshTokenRotation) and the /connect/revoke revocation endpoint
(EnableRevocation) are on by default — keep them on. When an already redeemed refresh token is
used again (replay), OpenIddict revokes the whole family itself: every token of that authorization.
Logging and audit
- Never log tokens / secrets / PII; enable transaction auditing.
action), the target and the outcome (result).
Two known limitations of the trail — account for them when you plan reporting and
incident analysis.Token issuance is not audited. Neither the initial issuance via authorization code nor
refresh-token rotation creates an audit record: the trail is written from transaction
events, and token issuance does not travel through them and is not tied to a transaction —
even less so rotation, which happens outside the transaction lifecycle altogether. Do not
build proof of token delivery on the audit trail: issuance and rotation are controlled by
token revocation and family revocation on replay (see
Refresh token rotation).The outcome of a channel security record is uninformative. For records produced by
channel security events the outcome field is always the same — failure — for any channel
code. A failure in such a row does not mean something broke: the meaning is carried by the
code in the
action field, and the codes are listed in the documentation of the specific
channel. Filter and analyze these records by action, not by outcome.Program.cs
Inbound verification level of a channel
-
InboundVerificationis declared for every channel section — and declared deliberately.
InboundVerification: a deployment with an enabled shipped
channel and no value does not start. The startup check walks the very set of
channels the core builds its enabled list from: all shipped channels are in it, a channel of a
third-party adapter plugged in with a bare AddChannel call is not — you have to check its section
manually. What matters is not whether the key is there but what it says: the value must match
the mode the channel actually runs in (the table is in Channel setup). A
mismatch here is silent — a channel in webhook mode declaring outbound_fetch starts without any
warning.
none is a deliberate choice, never a default: it says “the authenticity of an incoming event
is not verified”, i.e. the confirmation endpoint of that channel accepts a request from anyone. Set
it only if you have genuinely accepted that risk, and record the decision where your other risk
decisions live.
Channel availability
- Define behavior when a channel is unavailable.
Localization and accessibility
- WCAG 2.2 AA; locale files for your audience’s languages.
build/docker/Dockerfile ships English, Russian and Chinese translations; in the embedded mode the locale
catalog is supplied by your host, and without it the page is shown in English (see
Localization). No formal accessibility audit with a
published report has been carried out — if your regulations require one, plan it on your side.
Versioning and migrations
- SemVer SDK; relational stores via migrations, not auto-create.
Operational essentials
- Health probe, encrypted DB connection, secret storage, external JS on the sign-in page off.
Next steps
OIDC + Veriqa explained
Understand exactly where Veriqa sits in the OIDC flow.