Skip to content
Software Modules Programming

Signing Platform Web App

By Romi Fadli, S.Kom. 10 min read Software Design
On this page
A memory chip holding a key, circled by refresh arrows, beside a browser window where a signature stamp is being dragged into place on a PDF page

Signing Platform Web App is the browser application of a document-signing platform: where people sign in, prove who they are, place their stamp on a PDF and sign it, manage their certificates, and approve the systems that sign on their behalf — and where administrators run organisations, users, reviewers, and client systems. It is a single-page application in Vue and TypeScript that talks to one backend, the platform's core API, and keeps a person signed in without ever storing their access token where a script could find it later.

I built it as the counterpart of the core: the core holds the rules and the data, and this application turns them into screens. Most of the decisions in it are about two things a signing product cannot get wrong — keeping a session safe without making people log in again and again, and putting a stamp exactly where the signer saw it.

At a glance

Language & frameworkTypeScript, with Vue 3, Pinia, vue-router, vue-i18n, Tailwind CSS, and Vite; pdf.js for documents
ArchitectureFeature-Based Frontend Architecture
Version2.13.2
StatusStable, in production
FormA single-page application served under its own path, beside a server-rendered public home page
Source codePrivate
What the module is and where it stands.

What it does

A. Signs documents in the browser

A document is uploaded straight to storage through a short-lived link and given its signers in ordered stages. The signer opens it in a PDF viewer with high-resolution rendering, zoom, and touch support, drags and resizes their stamp on the page, chooses a certificate, enters a reason, a location, and their passphrase, and signs. A public page verifies any signed PDF dropped, pasted, or chosen from disk.

B. Keeps people signed in, safely

A session survives reloads and expired access tokens without asking for the password again, and without the token ever being written to browser storage. Sign-in works with a password, with external identity providers, with two-factor authentication, and with invisible bot protection.

C. Proves identity

Before someone can hold a signing certificate, they submit their personal details and two photos, and complete a live face challenge embedded in the page. A status card and banner tell them where their verification stands, prompting them at most once per session.

D. Manages stamps and certificates

Signers draw or upload their signature image or generate a QR stamp, request certificates protected by their own passphrase, and see each certificate's status. Conflicts, such as a certificate that already exists, are explained in dedicated dialogs rather than a bare error.

E. Approves the systems that sign through it

When a client system wants to act for a user, the user sees exactly which system and which permissions, and allows or denies in a consent dialog. Client systems can also send a user straight to a document to sign and receive them back when it is done. Users can see and revoke every system bound to their account.

F. Runs the platform for administrators

Administrators get a dashboard tailored to their role, and manage organisations and their units, members and e-mail allowlists of up to a thousand addresses, users, identity reviewers, account managers, certificates, and client systems. Destructive or privileged actions ask for the administrator's password again.

Interface

AreaWhat a person can do there
Sign-inlog in, register, verify e-mail, reset a password, use an external provider, complete two-factor
Documentsupload, add signers in stages, place a stamp, sign, download the signed file
Verifycheck any signed PDF, without an account
Profilepersonal details, password, two-factor, identity verification
Stamps and certificatessignature images, QR stamps, certificate requests and status
Clientsconsent to client systems, review and revoke them
Administrationdashboard, organisations, users, reviewers, certificates, client systems — on desktop screens
The areas of the application; every route is loaded only when it is first visited.

Standards

StandardWhat it guarantees here
OAuth 2.0 and OpenID Connect with PKCE (RFC 7636)sign-in through external providers without a client secret in the browser, with state, nonce, and issuer checks
JSON Web Tokensshort-lived access tokens held only in memory and renewed silently
TOTP (RFC 6238)two-factor authentication with any authenticator app, set up from a QR code
PDF, rendered with pdf.jsdocuments shown exactly as they will be signed, with stamp positions converted into PDF coordinates
Window messaging (postMessage)the embedded face challenge reports its result to the page that opened it
Strict TypeScriptevery build type-checks the whole application before it is produced
The standards the application follows, and what each one guarantees.

Inside

The application follows Feature-Based Frontend Architecture: each feature — sign-in, documents, profile, stamps, certificates, clients, dashboard, administration — has its own views, components, and composables, and features never import each other. Components never call the network themselves; every request goes through one service layer with one HTTP client, and session and interface state live in a few shared stores. The visual design comes from a separate design-system package behind an alias, so the whole look can be exchanged without touching a feature.

Design decisions

The access token lives in memory and nowhere else. I keep the session in memory, inside a Pinia store, and let a refresh credential that no script can read carry it across reloads. When the application starts, it asks for a fresh token before the router is even installed, so a reload lands the person back where they were, signed in, and a failed refresh simply starts them signed out.

One refresh, however many requests fail at once. Access tokens are short-lived, so several requests often discover an expired token at the same moment. The HTTP client catches the first unauthorised answer, starts a single refresh, and parks every other failing request in a queue; when the new token arrives, each queued request is retried with it. An early version retried queued requests with the stale header they had captured, and failed again; now the new token is handed to each one explicitly. If the refresh itself fails, the session is cleared and the person is taken to the sign-in page.

External sign-in follows the specification to the byte. Sign-in through an external provider uses PKCE with a verifier generated from random bytes and encoded in URL-safe base64. My first version used ordinary base64, which a strict identity provider rejected as an invalid code verifier. State and nonce are random, the issuer is checked on the way back to defend against provider mix-up, and every temporary value is cleared once the exchange succeeds or fails.

A stamp lands exactly where the signer placed it. The viewer draws pages at the screen's real pixel density, so a stamp placed on a sharp display is not misplaced by the scaling between screen pixels and canvas pixels — a bug I fixed early. When the signer confirms, the stamp's box is converted from screen coordinates, measured from the top-left in pixels, into PDF coordinates, measured from the bottom-left in points, together with the page number, using the viewer's own conversion. The stamp keeps its proportions when resized, and stamp previews sit on pure white, because a checkerboard background hid pen-drawn signatures.

Consent and handover by redirect, not by embedding. Client systems once embedded a signing page in a frame. I replaced that with redirects: a client sends the user to the application with a one-time token, the user consents or signs inside the application they already trust, and is sent back to the client with the original state and the outcome. Consent moved from a full page to a dialog on the clients screen, where the user also sees every system already bound to them.

The design system is a package, not a folder of styles. The application was rebuilt in phases into feature modules on top of a separate design-system package. The package is referenced through an alias, and switching the alias to another build of the theme re-skins the whole application — tested by doing exactly that. I tried consuming it as a git submodule and went back to a compiled copy kept in the repository, so the theme's source can stay private.

Every label follows the active language. All interface text moved into translation files in ten measured steps. Column and navigation definitions that had been created once at start-up became computed values, because otherwise their labels froze in whatever language the application booted with. A script cross-checks every key used in code against the translation files, since the type system cannot. One bulk edit produced a malformed binding that silently hid the buttons of a dialog — the identity submit button disappeared — and neither the type check nor the build caught it, so that pattern is now searched for explicitly. Text that ends up inside a signed PDF, such as the stamp's wording, is deliberately not translated: it becomes part of a permanent document.

The browser bundle receives only what is meant for it. Only variables with the application's public prefix reach the bundle; each is typed and normalised in one configuration module.

Every screen knows its place. The public home page moved out of this application into the server-rendered core, so search engines can index it, and the application now lives under its own path. Every route is loaded only when visited. The administration area is meant for desktop screens and says so on smaller ones, while the signing screens are built for touch, with a bottom navigation bar and room for the phone's safe areas. Search fields share one debounced input that never loses focus while results load.

No answer reveals what exists. Password resets and verification e-mails always show the same message, whether or not the account exists, and links that arrive by e-mail are checked against the API's own origin before the application follows them.

Specifications

Talks toOne backend — the platform's core API — with credentials included for session renewal; files go directly to storage through short-lived links
SessionAccess token in memory only; silent refresh on start; one queued refresh for concurrent failures
Sign-inPassword, external providers with PKCE, two-factor with recovery codes, invisible bot protection
DocumentsPDF up to 50 MB; signers in ordered stages; stamp placement converted to PDF coordinates
InterfaceDark and light themes; touch-friendly signing screens; administration on desktop; translations with key cross-checks
BuildVite with strict TypeScript type-checking on every build; routes split per page
What the application needs, holds, and delivers.

The core API behind every screen: tokens, data, and the rules of the platform.

The face challenge embedded in the identity verification form.

The signer that produces the long-term PDF signatures this application places, downloads, and verifies.

Terms

Single-page application
A web application that loads once and then changes its screens in the browser, asking the server only for data.
Refresh token
A long-lived credential used only to obtain new short-lived access tokens, here kept where no script can read it.
Interceptor
Code that sees every request or response of an HTTP client and can change, retry, or reject it.
PKCE
Proof Key for Code Exchange: a one-time secret that proves the application finishing a sign-in is the one that started it.
Design system
A shared set of styles and components that gives every screen the same look and behaviour.

Technologies covered

  • TypeScript
  • Pinia
  • Vite
  • Vue
  • OAuth 2.0

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

A purple root circle linked to a blue and an orange intermediate circle, which lead to a signed document with a green seal and to a clock.

Aurograf

DevOps

The digital signature platform of the City Government of South Tangerang: signers prove who they are, sign PDFs to PAdES B-LTA with certificates from the platform's own authorities, and anyone can verify the result — version 2, which I designed, built and operate.

10 min read Read more →

Related Articles

A central core holding people, organisations, documents, and certificates, connected to eight surrounding services for storage, addresses, certificates, timestamps, the root, signing, thumbnails, and face challenges

The Laravel core of a document-signing platform: it owns users, identities, organisations, client systems, documents and their signed versions, stamps, and certificates, and orchestrates the services that sign, timestamp, render, and store — without ever holding a file.

9 min read Read more →
A signed PDF with a QR stamp, wrapped in four nested frames for the signature, its timestamp, the embedded validation data, and the archive timestamp

A Python service that signs PDFs to PAdES B-LTA with a visible stamp — embedding the timestamp, certificates, and revocation proof, then an archive timestamp — so signatures stay verifiable long after their certificates expire, and verifies signed PDFs.

10 min read Read more →
A document's fingerprint passing through a clock, signed by a certificate below it, and returning as a time seal stamped on the same document

A Rust timestamp authority that stamps a document's hash with a signed, NTP-backed time per RFC 3161, so PDF signatures stay verifiable after their certificates expire — issuing and renewing its own signing certificate, with public OCSP and CRL.

9 min read Read more →