Guides
Getting started
Run the backend and the workstation locally, enrol a security key and sign your first file.
You need a JDK 17, Node 22, and a FIDO2 security key. Everything else the build fetches.
Prerequisites
| Requirement | Why |
|---|---|
| JDK 17 | The backend and this docs site both pin JavaLanguageVersion.of(17) |
| Node 22 | Matches node:22-alpine in the frontend Dockerfile |
| A FIDO2 authenticator | A USB security key, or a platform authenticator such as Touch ID or Windows Hello |
A browser on localhost |
WebAuthn needs a secure context; http://localhost counts as one, other plain-HTTP hosts do not |
No database to install for local work. The deployed backend runs on PostgreSQL, but the dev profile overrides the datasource to an embedded H2 file under the backend working directory, so quarkusDev needs no infrastructure.
Run the backend
cd mobilesigner-webauthn-backend-demo
./gradlew quarkusDev
Quarkus starts on port 8090. On the first start DemoDataSeeder runs and loads the working set described in Seed data — eight files, twenty notings, six devices, twelve audit rows and two signature records. The guard is if (GovFile.count() > 0) return;, so it seeds once and is skipped on every later start against the same database file.
Confirm it came up:
curl -s localhost:8090/api/dashboard
{"pendingFiles":5,"approvedToday":0,"rejectedToday":0,
"totalDevicesActive":3,"totalAuditEvents":12,"avgApprovalMinutes":3600}
avgApprovalMinutes is 3600 — the mean of the two seeded approvals, which took three days (ITE&C/SEC/2026/0198) and two days (PR&RD/MGNREGS/2026/2031) from initiation to signature.
approvedToday is 0 on a fresh seed because the two seeded approvals are dated six and four days back. It only moves once you sign something.
Run the workstation
cd mobilesigner-webauthn-next-demo
npm install
npm run dev
predev runs npm run sdk:build first, so packages/web-sdk is compiled before Next starts. Skipping this is the usual cause of an unresolved @mobilesigner/web-sdk import.
Open http://localhost:3000. The Next server proxies to the backend through the two rewrites in next.config.mjs, so leave the backend on 8090 or set BACKEND_URL before starting — it is read at build and dev-server startup, not per request.
Sign your first file
- Open File inbox and pick any file with status Pending.
FIN/DBT/2026/0912is a good one — it isMOST_IMMEDIATE, so it sorts to the top. - The action rail on the right shows the decision controls disabled, with a token panel above them. Enter a username —
demo.usermatches the seeded noting history — and press Enrol token. - Your browser prompts for the authenticator. Touch the key. This is
navigator.credentials.create()throughwindow.WebAuthn; nothing is simulated. - The decision controls unlock. That unlock is driven by
GET /api/sessionreturningauthenticated: true, so it is the honest signal that the assertion was accepted and the encrypted session cookie is set. - Type a noting in the textarea and press Approve & Sign.
What happens on that click:
graph TB
H["sha256(file.body)<br/><small>64 hex characters, computed in the browser</small>"]
P["POST /backend/files/{id}/action<br/><small>{ action: APPROVE, note, documentHash }</small>"]
X{{"proxied to POST /api/files/{id}/action"}}
A["FileResource.act()<br/><small>appends GREEN note, sets status APPROVED,<br/>persists Approval, records FILE_APPROVED</small>"]
R["returns the full FileDetail<br/><small>the UI re-renders from that response</small>"]
H --> P --> X --> A --> R
classDef edge fill:#e8f0f9,stroke:#1d4e89,color:#18221d
classDef ok fill:#e4f4ec,stroke:#0d6b4f,color:#18221d
class X edge
class R ok
The response is the complete refreshed file, not an acknowledgement, so the status badge and the new noting-sheet entry appear together.
- The signature panel shows the digest. Follow Verify this signature to
/verify?hash=…and you get six PASS checks, becausePOST /api/verifyfinds theApprovalrow your click just wrote.
Then look at /audit. Your FILE_APPROVED row is at the top, and a SIGNATURE_VERIFIED row sits above it from the verification you just ran.
Try the other two decisions
Approve is the happy path, but a file that only ever gets approved is not a government workflow.
- Reject requires a note and writes a GREEN note, because a rejection is a decision of record.
- Return for clarification requires a note and writes a YELLOW draft note, because the file is going back down the chain rather than being disposed of.
Neither requires a document hash. FileResource.act() only enforces the SHA-256 check on APPROVE:
if (action.equals("APPROVE") && (documentHash == null || !SHA_256.matcher(documentHash).matches())) {
return Response.status(400).entity(new ErrorResponse("A valid SHA-256 document hash is required to sign")).build();
}
EDN/INFRA/2026/0663 ships already in the RETURNED state with a YELLOW note explaining what was missing, so you can see the outcome without producing it.
Resetting
The seeder is guarded on GovFile.count(), so to get back to a clean working set you delete the database file:
cd mobilesigner-webauthn-backend-demo
rm -rf data/
./gradlew quarkusDev
This also destroys your enrolled credential, so you will re-enrol on the next run. A pod restart in Kubernetes has the same effect, because no volume is mounted there either — see Operations.
Type-checking
npm run typecheck # builds the SDK, then tsc --noEmit across the app
./gradlew test # 28 backend tests against a throwaway PostgreSQL
npm run verify:bridge # 13 end-to-end checks against a running deployment