@qldc/auth
Verify QLDC workforce and Entra External ID access tokens in a Node API.
The package checks signature, an explicit issuer, audience, expiry, and oid. An External ID token is public and is rejected when its email is on the caller's work-email domain list. A workforce token is staff. There is no default issuer: an app passes the issuers it trusts, and a blank audience, issuer, or work-email list fails at startup.
Each SPA keeps its own MSAL setup. km-auth-extensions keeps its own check of Microsoft's extension-service token.
Install
npm install @qldc/auth
Local sibling checkout:
{
"dependencies": {
"@qldc/auth": "file:../km-auth"
}
}
Usage
import {
QLDC_WORK_EMAIL_DOMAINS,
createExternalIdVerifier,
createWorkforceVerifier,
externalIdIssuer,
workforceIssuers,
} from "@qldc/auth";
const tenantId = process.env.ENTRA_TENANT_ID!;
const audience = process.env.ENTRA_AUDIENCE!;
const verifyWorkforce = createWorkforceVerifier({
audience,
...workforceIssuers(tenantId),
debug: process.env.NODE_ENV === "development", // optional: enable error logging
});
const verifyExternalId = createExternalIdVerifier({
audience: process.env.EXTERNAL_ID_AUDIENCE!,
issuer: externalIdIssuer(process.env.EXTERNAL_ID_TENANT_ID!),
workEmailDomains: QLDC_WORK_EMAIL_DOMAINS,
debug: process.env.NODE_ENV === "development", // optional: enable error logging
});
const staff = await verifyWorkforce(token); // AuthPrincipal | null
const pub = await verifyExternalId(token);
A workforce-only app creates createWorkforceVerifier and does not create the External ID verifier. workforceIssuers includes the v1 issuer (https://sts.windows.net/{tenant}/). Omit v1 when the app does not accept those tokens.
externalIdIssuer uses https://{tenant}.ciamlogin.com/{tenant}/v2.0. The sign-in host signin.qldc.govt.nz is not the token issuer.
Audience accepts the GUID and api://{guid} form of the same app registration. Other api:// values must match the token exactly.
QLDC_WORK_EMAIL_DOMAINS is qldc.govt.nz, qlcht.org.nz, and alpinewaters.co.nz. The match includes subdomains. The verifier uses that list only when the caller passes it.
A verified principal:
| Field | Meaning |
|---|---|
capability | staff or public |
oid | Entra object id. Key public user data by this. |
email | email, upn, preferred_username, or unique_name |
roles, scp | Workforce app roles and scopes, when present |
A token that fails the check returns null.
Debug mode
Enable debug: true in development to log verification failures to the console:
const verifyWorkforce = createWorkforceVerifier({
audience,
...workforceIssuers(tenantId),
debug: true,
});
Debug logs include:
- Invalid JWT format errors
- Token signature failures
- Issuer/audience mismatches
- Expiration issues
- Work email rejections (External ID only)
Advanced types
For observability and monitoring, the package exports additional types:
import type {
VerificationResult,
VerificationFailureReason,
TokenVerifierWithResult,
} from "@qldc/auth";
These types support building custom verifiers with structured error responses rather than just null.
Publish (maintainers)
CI uses npm Trusted Publishing (OIDC) — no long-lived NPM_TOKEN.
- On npmjs.com → package → Settings → Trusted Publisher: wire the GitHub org/repo and
publish.yml - Bump version and push a tag; Actions publishes:
npm version patch # or minor / major
git push --follow-tags
Scripts
npm test
npm run build
npm run typecheck