Secure Connect API Development by Sourav SharmaSecure Connect API Development by Sourav Sharma

Secure Connect API Development

Sourav Sharma

Sourav Sharma

Secure Connect

Secure Connect is a Node.js/Express middleware API that automates physical access control by integrating with the Gallagher security and monitoring platform. It exposes simplified, versioned, JWT-protected REST endpoints for managing cardholders — onboarding, credential (Access/MSIC card) issuance, access-group assignment, updates, and offboarding — instead of provisioning each one by hand in Gallagher's admin console.
The integration itself is built behind a Service + Adapter pattern, so Gallagher is treated as one pluggable backend rather than a hardcoded dependency — additional access-control vendors could be added without touching the core cardholder logic.

Why this exists

Manually provisioning physical access (creating a cardholder, issuing a card, assigning them to the correct access group, later revoking a lost card) in a vendor console doesn't scale and isn't auditable. Secure Connect turns that workflow into a small set of authenticated REST calls, with structured, PII-redacted logging for every request so actions are traceable after the fact.

Key Features

JWT authentication — every cardholder/cache-management endpoint requires a valid Authorization: Bearer <token> issued by POST /api/v1/auth/login. Passwords are stored as bcrypt hashes; login attempts are rate-limited per IP to slow brute-force attempts. This is Secure Connect's own gate — Gallagher itself has no concept of JWT; it only ever sees the separate Gallagher API key described below.
Gallagher integration — mutual-TLS (client certificate, optional if Gallagher CC is configured to accept clients with none) plus API-key authentication to Gallagher's REST API, with an in-memory cache of resource hrefs (cardholders, divisions, access groups, operator groups) to minimize discovery calls.
Per-operator Gallagher attributioncreate_cardholder/update_cardholder/delete_cardholder accept an optional X-Gallagher-Api-Key header. When present, that request's Gallagher-side action is attributed to that specific Gallagher REST Client identity instead of the shared server-configured key — useful when multiple operators use the same Secure Connect deployment but should show up distinctly in Gallagher's own audit trail. Falls back to the server's GALLAGHER_API_KEY when omitted. This is a separate, independent credential from the JWT bearer token — do not confuse the two headers.
Service + Adapter architecture — business logic (services/CardholderService.js) is decoupled from the vendor-specific client (api/gallagher/GallagherAdapter.js), so new integrations can be added by implementing the same adapter interface.
Request validation — Zod schemas validate every incoming payload (card types, card-number format, ISO date ordering, required fields) and return structured 400 errors.
Structured, audit-ready logging — Winston with daily-rotating file transports, correlation IDs propagated across a request's lifecycle, and automatic redaction/masking of names, emails, and card numbers in logs.
API versioning — all routes are namespaced under /api/v1 to allow non-breaking evolution.
Dockerized — ships with a Dockerfile for containerized deployment.
API console — a React + Material UI + Vite front end (with a Monaco JSON editor) for exercising every endpoint, including the login flow.

Architecture

Loading

Data Flow — creating a cardholder

Loading

Authentication Flow

Loading

How to Integrate a New Access-Control API

The Adapter Pattern makes it straightforward to add another vendor alongside Gallagher:
Create a new adapter in api/<vendorName>/<VendorName>Adapter.js, implementing the same public methods as GallagherAdapter.js (createCardholder, updateCardholder, deleteCardholder, findCardholderHrefByFirstName, findDivisionHrefByName, findCardNumberHref).
Implement the adapter's HTTP/auth logic for that vendor's API.
Wire it into the routes in routes/v1/cardholderRoutes.js, selecting an adapter based on a header, query parameter, or body field.

Project Structure


Setup

Clone the repository

Install dependencies

Configure environment variables
Copy the template and fill in real values:

At minimum you'll need:

If Gallagher Command Centre runs on the same machine as this app, use its LAN IP (from ipconfig/ifconfig) for GALLAGHER_API_URL, not 127.0.0.1/localhost — Command Centre's REST Client IP allowlist matches on the interface address, and loopback connections get rejected with a bare 401.
DEFAULT_DIVISION_NAME, DEFAULT_ACCESS_GROUP_ID, GALLAGHER_ACCESS_CARD_TYPE_ID, and GALLAGHER_MSIC_CARD_TYPE_ID must exactly match resources that already exist in your Gallagher instance (Configure > Divisions / Access Groups / Card Types in the Gallagher Configuration Client) — every one of these is instance-specific and create_cardholder will fail with Gallagher's own validation message if any of them don't match. Fetch your instance's real IDs with:

Card number format is also enforced by Gallagher itself, on top of this app's own 6-9 alphanumeric validation — some instances only accept numeric card numbers for Access cards. If create_cardholder returns Invalid card number '...', try a numeric-only value.
Certificates folder (optional)
Only needed if Gallagher CC requires client certificates. Create a certificates/ folder at the project root and place your Gallagher mTLS client certificate (.pfx) inside. This folder is gitignored — never commit certificate files.
Run the server

nodemon.json restricts the dev auto-reload watcher to the actual source directories (server.js, routes/, middlewares/, services/, utils/, api/, config/gallagher.js) so unrelated file activity elsewhere in the repo doesn't trigger restarts and silently reset the in-memory Gallagher cache.
Run the API console (optional)

API Reference (/api/v1)

Authentication

Login

POST /api/v1/auth/loginpublic, rate-limited (5 attempts / 15 min / IP)
Body:

Response:

All routes below require Authorization: Bearer <token> from this response.

Cardholder Management

Validation rules are unified across create/update/delete via validatePersonBody in routes/v1/cardholderRoutes.js. The request body must be an object with a top-level person key.
Cardholder schema (person)
Required: firstName (non-empty), cards (array, min 1)
Optional: lastName, email (valid email), divisionName, employmentCategory, photo (base64 JPEG, raw or data URI)
Card schema (each item in cards)
cardType: "Access" or "MSIC"
cardNumber: ^[A-Z0-9]{6,9}$ (case-insensitive input, normalized to uppercase), unique per request
activationDate / expiryDate: ISO 8601 datetime, expiryDate strictly after activationDate

Create Cardholder

POST /api/v1/create_cardholder

Update Cardholder

PATCH /api/v1/update_cardholder

type (optional) updates the Access card's status via a JSON Patch–style update to Gallagher.
The cardholder is located by person.firstName.

Delete Cardholder

DELETE /api/v1/delete_cardholder

Locates the cardholder by firstName and deletes the first match — if multiple cardholders share a first name, disambiguate upstream in Gallagher before deleting.

Cache Management

GET /api/v1/cache_status — cache initialization state and cached hrefs. Self-warms the cache on a fresh process if it isn't initialized yet, so it doesn't just report an empty cache.
POST /api/v1/clear_cache — clears the in-memory href cache
GET /api/v1/cached_hrefs — returns cached Gallagher endpoint hrefs (also self-warms)
The href cache is in-memory per process — it resets on every restart, which is expected; the routes above re-populate it automatically on next use.

Error Responses

Validation failures (caught before any Gallagher call) return 400:

Errors from Gallagher itself (e.g. an invalid division, card type, or duplicate card number) also return the upstream status code, with Gallagher's own message surfaced directly rather than a generic axios error:

Missing/invalid/expired tokens return 401. Exceeding the login rate limit returns 429. Unhandled server errors return 500.

Known Limitations

update_cardholder currently requires a non-empty cards array in every request, even if you only want to change lastName/description/etc. and aren't touching cards. This is a validation gap (the schema is shared with create_cardholder), not a Gallagher limitation.
delete_cardholder matches by firstName only and deletes the first result — if more than one cardholder shares a first name, disambiguate in Gallagher first.
There's no way to change an existing card's type (Access ↔ MSIC) — Gallagher models that as issuing a new card, not editing one. update_cardholder's type field only changes an Access card's status (e.g. Lost/Active).

Security Notes

Gallagher credentials (API key + client certificate passphrase) live only in config/secrets.env, which is gitignored — they are never sent from the browser.
All names, emails, and card numbers are redacted or masked before being written to logs (utils/logger.js).
The bundled login is a single configured account intended for a demo/portfolio deployment; swap AUTH_USERNAME/AUTH_PASSWORD_HASH for a real user store before using this in production with multiple operators.
Contributions and suggestions are welcome — open an issue or a pull request.
Like this project

Posted Aug 11, 2026

Secure Connect: Automating Physical Access Control for Enterprise Security Systems