sg-gov-singpass-legacy · git:20261027.6f21efa · 2026-10-27 · sha256 69826905bf2e200a
sg-gov-singpass-legacy git:20261027.6f21efaA
Immutable. This exact content never changes and is served at /api/v1/blob/69826905bf2e200a.
---
name: sg-gov-singpass-legacy
description: >-
Maintain, debug, or migrate a LEGACY Singpass integration — Myinfo v3
(client_secret + PKI_SIGN X.509 signing, /com/v3/authorise), Myinfo v4
(OAuth 2.1 client assertions + DPoP, /com/v4), or the pre-FAPI 2.0 Singpass
authentication API (direct /auth redirect, comma-separated sub like
"s=S1234567A,u=uuid"). Use this whenever an existing Singpass or Myinfo
integration predates Feb 2026, when code references PKI_SIGN, client_secret,
api.myinfo.gov.sg, attributes=, purpose_id, person/{sub}, or parses NRIC out
of the sub claim — and when planning the mandatory migrations: Myinfo v3/v4
to v5 by end Sep 2026, and FAPI 2.0 compliance for all Singpass apps by
31 Dec 2026. For new integrations use the sg-gov-singpass skill — legacy onboarding
is discontinued.
---
# Singpass legacy: Myinfo v3/v4 and the pre-FAPI authentication API
Three legacy surfaces are still in production across agencies. Identify which
one you are working on before changing anything:
| Surface | Endpoints | Client auth | Payload crypto |
|---|---|---|---|
| **Myinfo v3** | `api.myinfo.gov.sg/com/v3/*` (`/authorise` — British spelling) | `client_id` + **`client_secret`** + `Authorization: PKI_SIGN` RS256 base-string signature keyed by an **X.509 RSA cert** | JWE **RSA-OAEP + A256GCM** wrapping RS256 JWS |
| **Myinfo v4** | `api.myinfo.gov.sg/com/v4/*` (`/authorize`) | ES256 client assertion (`aud` = the API URL, adds `cnf.jkt`) + **DPoP**; no secret, no X.509 | JWE **ECDH-ES+A256KW + A256GCM** wrapping ES256 JWS |
| **Pre-FAPI Singpass auth API** | `id.singpass.gov.sg` — `/auth`, `/token`, `/userinfo` (no `/fapi` prefix, no PAR) | `private_key_jwt` (`aud` = bare issuer URL), PKCE S256 | ID token JWS, or JWS-in-JWE (**ECDH-ES+A\*KW + A256CBC-HS512**) for PII clients |
The JWE recipes differ across all three, so decryption code is not portable
between them. Client assertions are not portable either (`aud` differs per
system).
**Deadlines:**
- **Myinfo v3/v4 → v5: end Sep 2026** (per the current Myinfo FAQ; the legacy v4
FAQ still shows a stale "30 Sept 2025"; the current docs date governs).
v3/v4 onboarding is discontinued and the official connectors are frozen.
- **All Singpass apps FAPI 2.0-compliant by 31 Dec 2026** (banner on every
pre-FAPI docs page).
- The last Myinfo v3 X.509 verification certificate **expired 7 Nov 2024**.
Running v3 services must have downloaded the renewed cert and toggled
"Renewed certificate" in the SDP app details, or signature verification breaks.
For anything new, use the **sg-gov-singpass** skill. This skill covers two jobs:
keeping legacy integrations running until migration, and executing the migration
without silent breakages.
## Source and currency
Embedded from the legacy sections of https://docs.developer.singpass.gov.sg as of
**2026-07-23** (append `.md` to page URLs for markdown). Full wire formats, error
tables, and migration checklists: `references/legacy-surfaces-and-migration.md`.
Caution: the "Technical Concepts" pages filed under the pre-FAPI docs tree are
FAPI-era content (they reference PAR/DPoP and the `/fapi` audience); don't treat
them as pre-FAPI-normative.
## Identification cheatsheet
You are on **v3** if you see: `client_secret`, `PKI_SIGN`, RSA private keys or
`.cer` files, `attributes=` (comma-separated), free-text `purpose`, `/authorise`,
epoch-millisecond `timestamp`/`nonce` header params.
You are on **v4** if you see: `/com/v4/`, `purpose_id`, `cnf.jkt` in the client
assertion, `DPoP` with token-URL `aud`, **no `state` parameter**.
You are on **pre-FAPI Singpass** if you see: direct `/auth` URL construction (no
PAR), `aud` = `https://id.singpass.gov.sg` (no `/fapi`), and the comma-separated
`sub`: `s=S1234567A,u=<uuid>` (SFA users: `s=...,fid=...,coi=...,u=...`; minimal
form `u=<uuid>`).
## Maintenance essentials per surface
- **v3**: the `client_secret` is a shared secret **and is included in the
signed base string**; treat leaks as full-credential compromise. The PKI
timestamp must be epoch-milliseconds and **monotonically non-decreasing**
(clock rollbacks break signing); nonces are single-use. Person calls put
`PKI_SIGN ...` and `Bearer <token>` comma-joined in ONE Authorization header.
ECC/ECDSA certs are not supported; certs are issued to your company, not a
domain; separate keys per environment.
- **v4**: access tokens are **one-time-use for /person** despite `expires_in`
1800 (error: `Duplicated DPoP-bound access_token`); fetch a fresh token per
call. New ephemeral DPoP key pair per transaction, but the /token and /person
proofs of one transaction must use the **same** key (bound via `cnf.jkt`);
`ath` only on /person. There is **no `state` param**; bind the flow to the
browser session yourself. Authorize session expiry is 2 minutes (nonstandard
HTTP 440).
- **Pre-FAPI**: authorization code lifetime **2 minutes**; access token reusable
for 30 minutes at `/userinfo` with **`Bearer`** (correct on this surface);
client assertion `aud` is the bare issuer; ID token is plain JWS for `direct`
clients and JWS-in-JWE only for `direct_pii_allowed` (NRIC in `sub`). Userinfo
payload `sub` must match the ID token `sub`.
- All surfaces: respect JWKS/X.509 rotation windows (1-hour caches,
3-second/3-retry fetch SLOs; failures surface as `invalid_client` during user
logins); no IP whitelisting anywhere (whitelist FQDNs); no cert pinning (pin
Amazon root CAs only if forced); TLS 1.2 with the four ECDHE-GCM suites.
## The migrations
### Myinfo v3/v4 → v5 (end Sep 2026)
v5 is the `/userinfo` flow on the FAPI 2.0 Singpass API — see the **sg-gov-singpass**
skill for the target. Key facts:
1. **New client_id required.** Use the SDP **"Duplicate app"** function (exists
only for v3/v4 apps): carries the config over with **no re-review or
re-approval**. Old credentials cannot be reused.
2. **Do not migrate v3 → v4.** v4 migration is no longer mandatory and gains
nothing; go straight to v5.
3. **Parameters that must NOT survive**: `authmode`, `purpose` / `purpose_id`
(purpose is configured in SDP now), `login_type`, `subentity_id`,
token-request `state`, `client_secret`, `Authorization: PKI_SIGN`.
`attributes` (comma-separated) becomes `scope` (space-separated, plus
`openid`); `appLaunchURL` → `app_launch_url`. v4 apps must **drop `cnf.jkt`**
from their client assertion.
4. **Crypto swap**: v3's RSA X.509 setup (RS256, RSA-OAEP+A256GCM) is replaced
by EC JWKS with `sig` + `enc` keys, ES256 client assertions (`aud` = FAPI
issuer), DPoP on all three calls, and JWE `A256CBC-HS512`. None of the crypto
code carries over, so budget time for it.
5. **`sub` semantics**: v3/v4 person `sub` is a UUID (the v3 sandbox returns real
UINFINs as `sub`; never generalize from sandbox); v5 keeps UUID `sub` and
moves NRIC behind the `user.identity`/`uinfin` scopes.
6. Compliance rules (display-as-is, purge, no caching, manual alternative) carry
over unchanged. The Duplicate path skips production re-review, but audits
still apply, so fix existing violations as part of the migration.
### Pre-FAPI → FAPI 2.0 (31 Dec 2026)
Same app and client_id; **API version is selected per flow** (PAR ⇒ FAPI 2.0,
direct `/auth` browser redirect ⇒ pre-FAPI), so you can stage the rollout behind
a flag. The deltas:
1. **Add PAR** (`POST {issuer}/fapi/par`, 60-second `request_uri`); the browser
URL shrinks to `client_id` + `request_uri`.
2. **Add DPoP** on PAR, token, and userinfo (ephemeral per-session key, `ath` at
userinfo); userinfo auth scheme changes `Bearer` → `DPoP`.
3. **Client assertion `aud` changes** from `https://id.singpass.gov.sg` to
`https://id.singpass.gov.sg/fapi`.
4. **ID token is always encrypted** now (JWE even for UUID-only clients), and
**`sub` becomes a bare UUID** — delete the `s=`/`u=`/`fid=`/`coi=`
comma-parsing; NRIC moves behind the `user.identity` scope. For SFA-aware
parsers, the `fid`/`coi` keys move into `sub_attributes`.
5. **Login apps must add `authentication_context_type`** (mandatory, anti-fraud
enum); code lifetime tightens 2 minutes → 60 seconds; `amr` values change
(`sms`→`otp-sms`, `fv`→`face`); read `acr` instead.
6. New-app UX rules apply on re-review: QR-only login, no WebViews.
## Migration audit checklist
| Check | Failure smell |
|---|---|
| Surface correctly identified (v3 vs v4 vs pre-FAPI) | "upgrade v3 to v4 first" plans; mixed crypto assumptions |
| Deadlines right: v3/v4→v5 end Sep 2026; FAPI 31 Dec 2026 | 2027+ timelines; trusting the stale 30-Sep-2025 FAQ date |
| v5 plan uses Duplicate-app (new client_id, no re-review) | plan reuses v3/v4 credentials |
| `client_secret`/PKI_SIGN/X.509 fully retired | secret kept "for compatibility"; RSA keys in the v5 design |
| `attributes`→`scope` + `openid`; purpose moved to SDP config | comma-separated scope strings; per-request purpose |
| Comma-`sub` parsing deleted; UUID-first identity | NRIC parsed from `sub`; positional `split(",")` |
| JWE code rewritten per target recipe | RSA-OAEP or A256GCM decrypt reused against A256CBC-HS512 |
| Client assertion `aud` per system | token-URL `aud` sent to the FAPI issuer or vice versa |
| v4 one-time-token and same-key DPoP rules honored until cutover | token reuse against /person; per-call key regeneration mid-transaction |
| Compliance debt (display/purge/no-cache/manual path) fixed | migration scoped as pure plumbing |
## What this skill does not cover
- **The target state** — the FAPI 2.0 flow, Myinfo v5 data catalog, SDP
onboarding, compliance/UX rules: the **sg-gov-singpass** skill. Read it alongside this
one when executing a migration.
- **Business users / Myinfo Business** — **sg-gov-corppass** (current) and
**sg-gov-corppass-legacy** (its own 2027 deadlines).
- **PDPA/NRIC policy** — **sg-gov-data-protection**.