| name | hitrust-verifido-integration |
| description | Implement HiTRUST VeriFido FIDO2/WebAuthn Passkey integration across a BFF (or RP service) and mobile/web client: VeriFido HTTP gateway, OTP-gated attestation, assertion login, token issuance, rpMessage/clientMessage, and txId echo. Use when integrating HiTRUST VeriFido, VeriFido API, hitrustpay FIDO, Passkey, WebAuthn, attestation, assertion, or VeriFido RP proxy routing. |
HiTRUST VeriFido Integration
Use this skill when implementing HiTRUST VeriFido full-stack integration —
a BFF/RP service that talks to VeriFido, plus a client that talks only to your BFF.
In scope: HiTRUST VeriFido API (/fido2/*), ApiKey auth, rpId/systemId,
UseRpProxy routing, ceremony tx correlation, OTP-gated bind, assertion login → app tokens,
platform passkey association (AASA / Digital Asset Links).
Out of scope: Generic FIDO without VeriFido; self-hosted WebAuthn servers;
payment/SPC flows unless explicitly requested.
This skill is self-contained. Do not require any external sample repository.
Adapt language, framework, OTP provider, and identity directory to the target project.
Read first
- This file — build order and non-negotiable rules
- reference-security.md — identity, ceremony, OTP, logging (mandatory)
- reference-bff-contract.md — app-facing API contract
- reference-verifido-api.md — VeriFido HTTP mapping
- reference-platform-prerequisites.md — RP domain, ADM, AASA, assetlinks
- reference-mobile-flutter.md — Flutter/WebAuthn client (adapt for other clients)
- verification-checklist.md — proof matrix before done
- reference-anti-patterns.md — common bugs to avoid (from production validations)
If the target repo already has partial FIDO code, inventory first; do not duplicate endpoints or bypass existing workflow seams.
Endpoint surface (do not confuse counts)
| Layer | Count | What |
|---|
| BFF app API | 10 routes | config, OTP send/verify, bind options/result, login options/result, binding-status, remove, client-message |
| VeriFido gateway | 6 methods | attestation options/result, assertion options/result, client-message, user remove |
| VeriFido upstream paths | 7 URLs | above 6 flows + rpMessage or clientMessage (proxy mode branch) |
| Client minimum | 8 required | all except config and client-message |
| Client recommended | +2 | config, client-message |
Route prefix is project-specific (e.g. /auth/fido, /app/auth/fido). Document the actual prefix in OpenAPI.
What a complete VeriFido integration means
Backend (BFF/RP) + client together must cover all of:
| Flow | Auth required | Outcome |
|---|
| Config | No | Expose non-secret rpId, systemId, connectorId |
| OTP send | Yes (Bearer) | Email (or SMS) OTP before binding |
| OTP verify | Yes | Mark account verified (TTL ~10 min) |
| Bind options | Yes + verified | WebAuthn attestation options |
| Bind result | Yes + verified | Credential registered; clear OTP state |
| Login options | No | WebAuthn assertion options |
| Login result | No | VeriFido verified → issue app tokens |
| Binding status | Yes | Whether passkey exists |
| Remove | Yes | Deregister passkey |
| Client message | No | Report WebAuthn client errors to VeriFido |
Missing any BFF endpoint or skipping OTP gating on bind is incomplete.
Platform association (AASA / assetlinks + VeriFido ADM) is also required for native passkeys — see reference-platform-prerequisites.md. API-green alone is not done.
BFF API host ≠ rpId domain is valid. Client must use rpId from options response; association files must list every environment rpId.
Architecture template (copy/adapt)
Client (WebAuthn)
→ App API Controller # HTTP, auth guards, username == token identity
→ Domain / Application # thin facade; no VeriFido wire details
→ Auth/FIDO Workflow # OTP gate, rate limit, error mapping
→ Workflow Adapter # verification store + auth repo + shared flow
→ FidoFlowService # attestation/assertion orchestration
→ VeriFidoGateway # transport boundary (maps to repository)
→ VeriFidoRepository # HTTP to VeriFido
→ AuthSessionIssuer # post-login token claims (project-specific)
Optional portable modules: shared Contracts (DTOs), Abstractions (gateway/store/issuer interfaces), Core (flow service). Keep VeriFido wire DTOs out of app OpenAPI.
Abstraction boundaries (keep portable)
| Interface | Responsibility | Portability |
|---|
IVeriFidoGateway | VeriFido HTTP only | Reuse across projects |
IFidoFlowService | FIDO2 step 1/2, txId validation, success codes | Reuse across projects |
IFidoTransactionStore | Optional server ceremony cache; noop if upstream + echo suffice | Reuse; see security rules |
IAuthSessionIssuer | Map verified identity → access/refresh tokens | Project adapter |
IFidoVerificationStateRepository | OTP-verified flag before bind (Redis/in-memory) | Reuse pattern |
IOtpProvider | Send/verify OTP codes | Project adapter |
IUserDirectory | Resolve user for token claims after assertion | Project adapter |
Keep VeriFido request/response DTOs in a shared contracts module. Do not leak VeriFido JSON shapes into the app OpenAPI. App-facing DTOs stay WebAuthn-shaped (challenge, txId, rpTxId, credential fields).
Project adapters (never hard-code a vendor)
| Adapter | Must decide for target project |
|---|
| OTP | Existing mail/SMS/auth service; cooldown + rate-limit mapping |
| User directory | How to look up member/employee after passkey login |
| Token issuer | JWT/session format, claims, scopes, client id |
| Bootstrap tenant | If login-time org context is unknown, use config — never hardcode |
| Lookup miss policy | Fail closed vs degraded claims — see reference-security.md |
Non-negotiable rules
1. Ceremony transaction correlation
Step 2 must accept echoed txId and rpTxId from Step 1.
- Noop store (default): reject Step 2 when either id is missing.
- Shared store: key must be per-ceremony (not global per flow type); atomic one-time consumption; confirm VeriFido replay/expiry guarantees or use store — see reference-security.md.
Client must persist Step 1 response until Step 2 completes (in-memory for single-screen flows is fine).
challenge is echoed for correlation; server validation is optional unless you store ceremony state.
2. OTP gate before bind
bind/options and bind/result require prior successful verification/verify. Check a TTL store (default 10 min). Clear store after successful bind.
3. Username identity lock (authenticated routes)
For authenticated endpoints, force input.username = authenticatedAccount. Reject mismatches. Never bind/remove another user's passkey.
4. Verified identity for token issuance (login)
POST /login/result is public. Request username is an untrusted hint for credential discovery only.
After VeriFido assertion success:
- Resolve identity from VeriFido
userData (authoritative).
- Reject (401) if request
username conflicts with verified identity.
- Map verified identity → directory → token claims.
- Prefer email-shaped identifiers for directory lookup when they come from verified sources.
See reference-security.md — never if requestUsername is email → use as token subject.
5. VeriFido routing consistency
UseRpProxy=true → client-message path /fido2/clientMessage
UseRpProxy=false → direct path /fido2/rpMessage
Validate at startup:
UseRpProxy=false + RP-proxy-like ApiBaseUrl → prod fail-closed
UseRpProxy=true + VeriFido-direct host → warn or fail (recommended)
6. VeriFido auth header
Every VeriFido POST: ApiKey: {rpId}||{apiKey} when ApiKey is configured. Redact apiKey in logs.
7. Success return codes
Treat 0, 0000, ok (case-insensitive) as success. Do not rely on HTTP status alone.
Build order
Copy this checklist and track progress:
HiTRUST VeriFido integration:
- [ ] 0. Platform prerequisites (RP domain, ADM, AASA, assetlinks)
- [ ] 1. Config + settings (RpId, SystemId, ApiKey, ApiBaseUrl, UseRpProxy)
- [ ] 2. VeriFido gateway + repository (6 methods → 7 upstream paths)
- [ ] 3. FidoFlowService (bind/login/remove/client-message)
- [ ] 4. OTP verification (send/verify) via project OTP provider
- [ ] 5. Verification state store (Redis or equiv., TTL 10m)
- [ ] 6. Auth/FIDO workflow (OTP gate, cooldown 300s, rate limit → 429)
- [ ] 7. App API controller (10 endpoints, auth rules)
- [ ] 8. AuthSessionIssuer + user directory (project-specific; lookup-miss policy)
- [ ] 9. OpenAPI / API contract (app-facing DTOs, camelCase)
- [ ] 10. Client (Flutter/native or web) + txId echo + optional account index
- [ ] 11. Tests per verification-checklist.md
Step details
0. Platform prerequisites
Complete reference-platform-prerequisites.md before expecting native passkey UI to work. Misconfigured association looks like “API works, OS never shows passkey.”
1. Settings
"VeriFido": {
"ApiBaseUrl": "https://t-fido.hitrustpay.com.tw/veri-fido-api",
"RpId": "<your-rp-domain>",
"SystemId": "<your-system-id>",
"ApiKey": "<secret-from-hitrust>",
"ConnectorId": "<optional-connector>",
"IsPayment": false,
"UseRpProxy": false
}
Load secrets from env / secret store. Never return ApiKey from /config.
3. FidoFlowService
- Step 1: generate
rpTxId (GUID), call VeriFido, return challenge + txId + rpTxId
- Step 2: require txId/rpTxId, submit credential, consume ceremony store entry if used
- Login Step 2: after VeriFido success → verify identity →
IAuthSessionIssuer.IssueAsync(authMethod: "passkey")
4. OTP
OTP is not VeriFido. Wire the project’s existing mail/SMS/auth service. On verify success, set verified flag in IFidoVerificationStateRepository. Ensure the verified account matches the Bearer identity.
8. Token issuer
Resolve user via the project’s directory using verified identity. Document lookup-miss policy explicitly (fail closed vs degraded claims). Log misses.
Client integration notes
Clients call only the BFF — never VeriFido directly. No VeriFido SDK required on device.
Proven mobile pattern (Flutter): custom MethodChannel → iOS ASAuthorization / Android Credential Manager. See reference-mobile-flutter.md.
Minimum client responsibilities:
- Echo
txId, rpTxId, challenge from options → result
- Use
rpId from options (not API host)
- Send
deviceType (ios | android | web)
- Native bridge should prefer server-returned WebAuthn options over hardcoded JSON
- Optional: local credentialId → email index for discoverable login UX (not authorization)
- Wire
/client-message on WebAuthn errors (recommended)
- Differentiate user cancel vs unbound vs network errors in UX
Common failure modes (avoid)
| Symptom | Likely cause |
|---|
| Step 2 "TxId is required" | Client didn't echo txId/rpTxId; noop transaction store |
| Bind 400 "not verified or expired" | Skipped OTP verify or TTL expired |
| Login 401 after VeriFido OK | Token issuer failed, identity mismatch, or Issued=false |
| Wrong user logged in | Request username trusted over VeriFido verified identity |
| Empty / wrong token claims | Directory lookup miss without documented fallback |
| client-message 404 | UseRpProxy mismatch with ApiBaseUrl |
| Bind wrong account | Missing username == token check in controller |
| OS never shows passkey sheet | AASA/assetlinks/ADM mismatch; rpId ≠ association domain |
| API green, passkey UI missing | BFF host OK but options rpId not in app association |
| Android Credential Manager fails | Activity not FragmentActivity; assetlinks cache stale |
| All errors show "not bound" | Client maps every PlatformException to unbound message |
Adapt vs reuse
| Reuse structurally | Adapt to target |
|---|
| 10-endpoint BFF surface | Route prefix, versioning |
| OTP-gated bind | OTP provider |
| Ceremony tx echo (+ optional store) | Redis ceremony store if needed |
| Flow service + gateway split | Language/framework |
| Gateway + issuer split | Token format, claims, user directory |
| Platform association pattern | Bundle IDs, signing fingerprints, domains |
When done
Run verification-checklist.md. Do not mark complete without:
- Contract tests for all 10 BFF endpoints
- Integration tests for auth guards, identity mismatch, and happy paths
- Ceremony replay / mix-up tests (or documented VeriFido guarantees)
- Startup validator for VeriFido routing (both proxy directions)
- Explicit decision on directory lookup fallback
- Platform association smoke (iOS and/or Android) against the real
rpId domain
- Client error-path differentiation (cancel vs unbound vs association)