6.1 KiB
Quickstart: OIDC Proxy Script Authentication
Feature: 001-oidc-proxy-script
Date: 2025-07-17
Prerequisites
- Node.js 18.0.0 or later (
node --version) - Access to a KME OIDC token service endpoint
- Project dependencies installed (
npm install)
Step 1 — Create adapter_settings.json
cd /path/to/kme-content-adapter
cp src/globalVariables/adapter_settings.json.example \
src/globalVariables/adapter_settings.json
Edit src/globalVariables/adapter_settings.json with your real credentials:
{
"tokenUrl": "https://auth.kme.example.com/protocol/openid-connect/token",
"username": "your-service-account@example.com",
"password": "your-password",
"clientId": "your-client-id",
"scope": "openid tags content_entitlements"
}
Security:
adapter_settings.jsonis in.gitignore. Never commit credentials. The.examplefile (with placeholder values) IS committed and serves as the template.
Step 2 — Start the Adapter
npm start
# or for development with auto-reload:
npm run dev
Expected startup log (structured JSON):
{"message": "Loaded global data: adapter_settings", "keys": ["tokenUrl","username","password","clientId","scope"]}
{"message": "Loaded 1 global variables", "json": 1, "js": 0}
{"message": "Configuration loaded", "port": 3000, "host": "0.0.0.0", ...}
{"message": "Configuration validated successfully"}
{"message": "Server listening", "port": 3000, "host": "0.0.0.0"}
Step 3 — Send a Test Request
curl -v http://localhost:3000/ProxyScript/run/67bca862210071627d32ef12/current/kmeAdapter
Expected response on first request (token fetched from OIDC service):
HTTP/1.1 200 OK
Content-Type: text/plain
Authorized
Expected response on subsequent requests (token served from cache):
HTTP/1.1 200 OK
Content-Type: text/plain
Authorized
No visible difference from the caller's perspective; the adapter log will show no new axios call for the second request (cache hit).
Expected response with invalid credentials:
HTTP/1.1 401 Unauthorized
Content-Type: text/plain
Unauthorized: HTTP 401
Step 4 — Run Tests
Unit tests (fast, no network, no server required)
npm run test:unit
# runs: node --test tests/unit/proxy.test.js
Exercises: cache hit, cache miss, token expiry, stampede prevention, timeout handling,
missing id_token, missing required fields, HTTP error from token service.
Contract tests (starts real HTTP server with mock token endpoint)
npm run test:contract
# runs: node --test tests/contract/proxy-http.test.js
Exercises: end-to-end 200 OK / Authorized, end-to-end 401 Unauthorized, verifies
response headers and exact body strings.
All tests
npm test
# runs: node --test tests/**/*.test.js
Architecture Summary
Inbound HTTP request
│
▼
server.js (http.createServer)
│ creates fresh vm.createContext({
│ ...globalVMContext, ← axios, URLSearchParams, console, ...
│ ...globalVariableContext, ← adapter_settings (from JSON)
│ req, res ← fresh per request
│ })
│
▼
proxy.js (vm.Script, compiled once)
│
├─ reads adapter_settings._cache
│ ├─ CACHE HIT: token valid → 200 OK / Authorized
│ └─ CACHE MISS / EXPIRED:
│ ├─ FETCHING (stampede guard): queue on _cache.pendingFetch → 200/401
│ └─ NEW FETCH:
│ POST tokenUrl (timeout: 5s)
│ ├─ SUCCESS: cache token + expiry → 200 OK / Authorized
│ └─ FAILURE: → 401 Unauthorized: <message>
▼
HTTP response to caller
Key Files
| File | Status | Purpose |
|---|---|---|
src/globalVariables/adapter_settings.json |
Create | OIDC credentials (gitignored) |
src/globalVariables/adapter_settings.json.example |
Create | Template with placeholder values |
src/proxyScripts/proxy.js |
Create | OIDC authentication proxy (VM sandbox) |
tests/unit/proxy.test.js |
Create | Unit tests (no network, no server) |
tests/contract/proxy-http.test.js |
Create | HTTP response contract tests |
src/server.js |
No change | Existing infrastructure; auto-loads adapter_settings.json |
config/default.json |
No change | Infrastructure settings only (port, host, log level) |
Token Lifecycle
| Phase | What happens |
|---|---|
| First request | _cache.token is null → fresh token fetch → cache id_token + expires_in |
| Subsequent requests (valid token) | Date.now()/1000 < _cache.expiry → return Authorized immediately, no network call |
| After token expiry | Date.now()/1000 ≥ _cache.expiry → fresh token fetch (transparent to caller) |
| Concurrent requests during fetch | All requests await the shared _cache.pendingFetch promise; only ONE HTTP call made |
| Auth failure | Clear _cache.token and _cache.expiry → respond 401 Unauthorized: <reason> |
| Timeout (5 s) | axios ECONNABORTED error → treated as auth failure → 401 Unauthorized |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
401 Unauthorized: HTTP 401 |
Wrong credentials | Check username, password, clientId in adapter_settings.json |
401 Unauthorized: connect ECONNREFUSED |
Token service unreachable | Check tokenUrl is correct and reachable |
401 Unauthorized: token service timeout |
Network slow or token service down | Verify connectivity; check token service health |
401 Unauthorized: id_token missing from response |
Token service returns access_token only |
Ensure openid is in scope and the service issues id_token |
Server fails to start with adapter_settings not found |
JSON file missing | Run Step 1 above |
SyntaxError in proxy.js at startup |
import or export statement in proxy.js |
Remove all import/export — proxy.js must be pure VM sandbox code |