繁體中文 | English
A working example of reading and writing NFC cards from a web browser — using a localhost connector architecture instead of the experimental Web NFC API.
Browser (HTTPS) ←→ Localhost Connector (Go) ←→ PC/SC ←→ NFC Reader
The Web NFC API is Chrome-only, Android-only, and still experimental. This project demonstrates an alternative approach: a lightweight Go service running on 127.0.0.1 that bridges any web app to a USB NFC reader via the standard PC/SC interface.
This architecture works on macOS, Windows (x64/ARM64), and Linux — anywhere a browser and a USB reader can coexist.
- Read — Extract UID and JSON-encoded NDEF data from NFC Type 2 cards
- Write — Write structured JSON payloads as NDEF records with read-back verification
- Real-time events — WebSocket stream for card presence, reader status, and operation results
- Security by design — HMAC-signed short-lived tokens, origin allowlist, no arbitrary APDU passthrough
- Cross-platform — PC/SC driver for macOS/Windows/Linux, plus a Direct IOCTL driver for Windows ARM64
┌─────────────────────────────────────────────┐
│ Browser │
│ Nuxt 4 + Nuxt UI (deployed to Cloudflare) │
└──────────────┬──────────────────────────────┘
│ HTTP / WebSocket
▼
┌──────────────────────────────────────┐
│ Localhost Connector (Go) │
│ http://127.0.0.1:42619 │
│ │
│ ┌────────────┐ ┌────────────────┐ │
│ │ PCSC Driver │ │ Direct Driver │ │
│ │ (all OS) │ │ (Win ARM64) │ │
│ └──────┬─────┘ └───────┬────────┘ │
└─────────┼────────────────┼───────────┘
▼ ▼
┌─────────────────────────────┐
│ NFC Reader (ACR1252U-M1) │
│ ISO-14443 Type 2 cards │
└─────────────────────────────┘
- Node.js 22+ and pnpm 10+
- Go 1.26+
- A PC/SC-compatible NFC reader (tested with ACS ACR1252U-M1)
- PC/SC service: built-in on macOS/Windows; install
pcscdon Linux
# Install frontend dependencies
pnpm install
# Terminal 1 — Start the web app
pnpm dev
# Terminal 2 — Start the connector
pnpm connector:devOpen http://localhost:3000 — the web UI will automatically detect the connector and list available readers.
# Build everything (web app + connector + installers)
pnpm build
# Or build individually
pnpm build:app # Nuxt web app (Node.js)
pnpm build:cf # Nuxt web app (Cloudflare Workers)
pnpm connector:build # Go connector binarypnpm installers:build:macos # macOS .pkg
pnpm installers:build:windows-x64 # Windows x64 .msi
pnpm installers:build:windows-arm64 # Windows ARM64 .msi
pnpm installers:build:linux-x64 # Linux .debBase URL: http://127.0.0.1:42619
| Method | Path | Description |
|---|---|---|
GET |
/health |
Connector health check |
GET |
/readers |
List available NFC readers |
POST |
/session/connect |
Start a reader session |
POST |
/card/read |
Read card UID + NDEF data |
POST |
/card/write |
Write NDEF payload to card |
WS |
/events |
Real-time event stream |
All endpoints (except /health) require an X-Bridge-Token header with a signed ticket issued by the web app's server route POST /api/connector-ticket.
See docs/connector-contract.md for full API details.
{
"sessionId": "1710000000000000000",
"operation": "ndef-v1",
"payload": {
"version": 1,
"type": "web-nfc-bridge/demo",
"label": "MO-20260309-001",
"content": {
"documentNo": "MO-20260309-001",
"itemCode": "FG-1001",
"workstation": "PACK-01",
"quantity": 24,
"status": "ready"
},
"updatedAt": "2026-03-09T01:35:40Z"
}
}Constraints:
contentmust be a flat JSON object (no nesting), max 8 fields- Values: string, number, boolean, or null only
- Max encoded NDEF size: 256 bytes
- Sensitive fields (
password,secret,email, etc.) are rejected
- No raw APDU — The connector only accepts pre-defined operations, never arbitrary card commands
- Short-lived tokens — HMAC-signed tickets expire in 60 seconds, scoped to
read,write,events, orall - Origin allowlist — Only whitelisted origins can communicate with the connector
- Payload validation — Write payloads are validated for structure, size, and forbidden fields before any card I/O
- Read-back verification — After writing, the connector reads the card back to confirm data integrity
| Layer | Technology |
|---|---|
| Frontend | Nuxt 4 + Nuxt UI + Tailwind CSS |
| Connector | Go + gorilla/websocket |
| Smart Card | ebfe/scard (PC/SC binding) |
| Deployment | Cloudflare Workers (web app only) |
| Platform | Architecture | Driver | Status |
|---|---|---|---|
| macOS | Apple Silicon / Intel | PC/SC | Validated |
| Windows | x64 | PC/SC | Validated |
| Windows | ARM64 | Direct IOCTL | Validated |
| Linux | x64 | PC/SC | Validated |
| Platform | Device | OS Version |
|---|---|---|
| macOS | Mac mini M4 | macOS Tahoe 26.3.1 |
| Windows x64 | Intel NUC | Windows 11 25H2 |
| Windows ARM64 | ASUS Zenbook A14 (Snapdragon X - X126100) | Windows 11 ARM 25H2 |
| Linux x64 | — | Ubuntu Server 24.04 |
- Reader: ACS ACR1252U-M1
- Card: NTAG 216 (NFC Type 2)
├── pages/ # Nuxt pages (web UI)
├── composables/ # Vue composables (connector communication)
├── server/api/ # Nuxt server routes (ticket issuance)
├── connector/
│ ├── cmd/nfc-connector/ # Go entrypoint
│ └── internal/
│ ├── api/ # HTTP + WebSocket server
│ ├── auth/ # Token verification
│ └── bridge/ # Driver layer (PCSC, Direct IOCTL, NDEF codec)
├── scripts/ # Installer build scripts
└── docs/ # Architecture & API documentation
Environment variables for the connector:
| Variable | Default | Description |
|---|---|---|
NFC_CONNECTOR_ADDR |
127.0.0.1:42619 |
Connector listen address |
NFC_CONNECTOR_SHARED_SECRET |
development-shared-secret |
HMAC signing key |
NFC_CONNECTOR_ALLOWED_ORIGINS |
localhost + 127.0.0.1 | Comma-separated origin allowlist |
- Architecture — System layers and design decisions
- Connector Contract — Full HTTP/WebSocket API specification
- Connector Operations — Operational details
- Platform Support — Platform validation matrix
Built by Yudefine - 域定資訊工作室