> For the complete documentation index, see [llms.txt](https://docs.developer.singpass.gov.sg/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.developer.singpass.gov.sg/docs/technical-specifications/migration-guides/migrating-mib-v1-v2-person-v3-to-myinfo-v5.md).

# Myinfo Business (v1/v2) Person v3 flows

{% hint style="warning" %}

### Who this guide is for

This guide applies only to existing **Myinfo Business (v1/v2)** partners whose **Myinfo Business application** retrieves **individual information** using the legacy Myinfo v3 APIs.

Specifically, this guide applies if your application currently uses the following endpoints:

If your integration uses these endpoints, this guide explains how to migrate your individual retrieval flow to **Myinfo (v5)** using **OpenID Connect (OIDC)** and the **FAPI 2.0 Security Profile**.
{% endhint %}

| Endpoint                   | Purpose                                             |
| -------------------------- | --------------------------------------------------- |
| `GET /com/v3/authorise`    | Authenticate the user and obtain consent            |
| `POST /com/v3/token`       | Exchange the authorisation code for an access token |
| `GET /com/v3/person/{sub}` | Retrieve the individual's Myinfo information        |

{% hint style="info" %}

### What is not covered

This guide covers **only** the migration of the **individual information retrieval flow**.

It does **not** cover the migration of **Myinfo Business entity APIs**, including endpoints such as:

* `GET /biz/v2/entity-person/{uen}/{uuid}`
* Other Myinfo Business entity APIs

If your application uses these APIs, refer to the [**Myinfo Business (v1/v2) to v3 (FAPI 2.0) Migration Guide**](https://docs.corppass.gov.sg/technical-specifications/corppass-authorization-api-fapi-2.0/migration-guides/migration-guide-myinfo-business-v1-v2-to-v3-fapi-2.0). The entity retrieval flow has its own migration path and should be migrated separately.

If your application uses both the legacy Myinfo Person APIs and the Myinfo Business entity APIs, you will need to follow **both migration guides**.
{% endhint %}

## Overview

Some Myinfo Business partners use their Myinfo Business application to retrieve both:

* **Business entity information** through the Myinfo Business APIs.
* **Individual information** through the legacy Myinfo Person APIs.

These two retrieval flows are independent and have separate migration paths.

This guide covers **only the migration of the individual information retrieval flow** from the legacy Myinfo v3 APIs to **Myinfo (v5)**.

Because the Myinfo (v5) security model differs significantly from the legacy implementation, you must onboard a new Myinfo (v5) application and obtain a new client ID. Existing Myinfo v3 applications and credentials cannot be reused. Given these changes, Singpass recommends implementing the new flow using a certified OpenID Connect Relying Party library instead of modifying your existing Myinfo Business v1/v2 integration.

The table below summarises the key differences between the legacy and new implementations.

| Legacy mechanism (v1 / v2 / v3)                                                       | New mechanism (v5 / FAPI 2.0)                                          |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Authorization request via browser redirect with query parameters                      | **Pushed Authorization Request (PAR)** — RFC 9126                      |
| `PKI_SIGN` request signing (RSA signature over a base string) with X.509 certificates | **Client assertion** (`private_key_jwt`) + **JWKS**                    |
| `client_secret` in the token request                                                  | **Client assertion** (no shared secret)                                |
| Bearer access tokens                                                                  | **DPoP-bound** authorization codes & access tokens                     |
| No PKCE                                                                               | **PKCE (S256)** — mandatory                                            |
| Person API: `GET /com/v3/person/{sub}?attributes=…`                                   | **`GET /userinfo`** (no path or query parameters)                      |
| Access token only                                                                     | Access token **+ encrypted ID token** (must be decrypted and verified) |

## Before you start: create a new Myinfo (v5) app

Your existing Myinfo v3 credentials — `client_id`, `client_secret`, APP ID and PKI certificates — **cannot be reused**. Complete onboarding first.

1. **Create a new Myinfo (v5) app** on the [Singpass Developer Portal](https://developers.singpass.gov.sg/). Create a [staging app](https://docs.developer.singpass.gov.sg/docs/singpass-developer-portal-sdp/app-management/create-staging-app) for testing, then a [production app](https://docs.developer.singpass.gov.sg/docs/singpass-developer-portal-sdp/app-management/create-production-app) for go-live.
2. **One purpose per app.** Myinfo (v5) apps must have a **single purpose**. If your current v3 app serves multiple purposes, create **one v5 app per purpose**. The purpose is now configured in the portal and is no longer sent in each request.
3. **Configure a JWKS** (JSON Web Key Set) instead of uploading an X.509 certificate. Your JWKS must contain at least **one signing key** (for client assertions) and **one encryption key** (so Singpass can encrypt the ID token to you).
4. **Register HTTPS redirect URIs.** Only `https://` URLs are accepted — see [Replace custom scheme URLs](#replace-custom-scheme-urls-mobile-apps-only) for mobile apps.
5. **Map your attributes to v5 scopes.** Translate your current `attributes` list into the equivalent scopes from the Singpass Data Catalog, and confirm the scopes allowed for your app during onboarding.

## General changes

These apply across the whole flow.

### Implement DPoP (Demonstrating Proof of Possession)

DPoP binds your authorization code and access token to an **ephemeral private key** known only to you, so a stolen code or token cannot be replayed by another party.

Your application must:

* Generate an ephemeral EC key pair (e.g. P-256) per authentication flow.
* Attach a signed **`DPoP` header** — a JWT containing the HTTP method (`htm`), target URI (`htu`), a unique `jti` and `iat` — to the **pushed authorization request**, the **token request**, and the **userinfo request**.
* Reuse the same key pair for all three calls in a single flow so the token stays bound to it.

See the [DPoP guide](https://docs.developer.singpass.gov.sg/docs/technical-specifications/technical-concepts/demonstrating-proof-of-possession-dpop) for the exact JWT structure.

### Replace custom scheme URLs (mobile apps only)

The new API **does not support custom scheme URLs** (anything that does not start with `https://`) as redirect or app-launch URLs.

If your Android/iOS app currently uses a custom scheme (e.g. `myapp://callback`):

1. Implement **app-claimed HTTPS URLs** — [Android App Links](https://developer.android.com/training/app-links) or [iOS Universal Links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app).
2. Register the new HTTPS URLs in the Developer Portal.
3. In the authorization request, set `redirect_uri` / `app_launch_url` to the new HTTPS URLs and add **`redirect_uri_https_type=app_claimed_https`**.
4. After deployment, remove the old custom-scheme URLs from your app configuration.

Note the rename: `appLaunchURL` → **`app_launch_url`**, which must be pre-registered in the portal.

## Step-by-step flow changes

### Step 1 — Authorization request: redirect → Pushed Authorization Request

**Old (v3):** your webpage redirected the browser to the authorise endpoint with all parameters in the query string.

**New (v5):** two sub-steps:

1. Send a **server-to-server `POST`** to the `pushed_authorization_request_endpoint` (published in Singpass's OpenID configuration), with an `application/x-www-form-urlencoded` body and a **DPoP header**. This returns a short-lived **`request_uri`**.
2. **Redirect the browser** to the authorization endpoint with only **two** query parameters: `client_id` and `request_uri`.

Parameter changes in the authorization request:

| Change                                                              | Detail                                                                                                                                             |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Remove** `authmode`                                               | No longer used                                                                                                                                     |
| **Remove** `purpose`                                                | Configured per app in the Developer Portal                                                                                                         |
| **Remove** `login_type`                                             | No longer used                                                                                                                                     |
| **Rename** `attributes` → `scope`                                   | Change from **comma-separated** to **space-separated**, and prepend **`openid`**. Old: `name,hanyupinyinname` → New: `openid name hanyupinyinname` |
| **Rename** `appLaunchURL` → `app_launch_url`                        | Must be pre-registered (mobile)                                                                                                                    |
| **Add** PKCE                                                        | `code_challenge` (base64url SHA-256 of your `code_verifier`) and `code_challenge_method=S256`                                                      |
| **Add** `nonce`                                                     | A unique, unguessable value per request; echoed back in the ID token and must be verified                                                          |
| **Keep** `state`, `redirect_uri`, `client_id`, `response_type=code` | `state` still protects against CSRF and reconciles the callback                                                                                    |

### Step 2 — Callback handling

Conceptually unchanged: the user is redirected to your `redirect_uri` with `code` and `state`. Continue to verify that `state` matches the value you sent. Error callbacks (`error`, `error_description`) still apply.

### Step 3 — Token exchange

**Old (v3):** `POST /token` with a `PKI_SIGN` Authorization header, plus `client_secret`, `client_id`, `code`, `redirect_uri`, `grant_type` and `state` in the body.

**New (v5):** `POST` to the token endpoint with these changes:

| Change                                               | Detail                                                                                                                                            |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Remove** the `Authorization: PKI_SIGN …` header    | No more base-string request signing                                                                                                               |
| **Remove** `client_secret`                           | Replaced by client assertion                                                                                                                      |
| **Remove** `state`                                   | Not supported in the token request body                                                                                                           |
| **Add** `client_assertion` + `client_assertion_type` | `client_assertion` is a JWT signed with your **JWKS signing key**; `client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
| **Add** `code_verifier`                              | The same verifier used to derive `code_challenge` in Step 1 (PKCE)                                                                                |
| **Add** `DPoP` header                                | Same key pair as used for the PAR                                                                                                                 |

**New — handling the ID token.** The token response now returns an **encrypted ID token** alongside the access token. You must:

1. **Decrypt** the ID token with your **JWKS encryption private key**.
2. **Verify** the inner JWS signature against Singpass's published JWKS.
3. **Validate** the `iss`, `aud` (your `client_id`), `exp` claims, and confirm `nonce` matches the value sent in Step 1.
4. If you need the user's **UUID** (`sub`), read it from the **ID token** — do **not** parse the access token.

{% hint style="info" %}
The access token has a lifetime of **30 minutes**.
{% endhint %}

### Step 4 — Person data retrieval: Person API → Userinfo

This is the core change for your team.

**Old — Myinfo v3 Person API:**

```http
GET /com/v3/person/{sub}?attributes=uinfin,name&client_id=YOUR_CLIENT HTTP/1.1
Host: api.myinfo.gov.sg
Authorization: PKI_SIGN app_id="...", nonce="...", signature_method="RS256",
               signature="...", timestamp="...",Bearer <access_token>
```

**New — Myinfo v5 Userinfo:**

```http
GET /userinfo HTTP/1.1
Host: id.singpass.gov.sg
Authorization: DPoP <access_token>
DPoP: <dpop_proof_jwt>
```

Required changes:

1. **Remove `sub` from the path.** The endpoint identifies the user from the token; you no longer parse the access token to build the URL.
2. **Remove the PKI signature.** Authentication is via DPoP + the access token only.
3. **Remove all query parameters** (`attributes`, `client_id`, `txnNo`). All data for the **scopes requested in Step 1** is returned — the request carries no attribute list.
4. **Change the token prefix** from `Bearer` to **`DPoP`**.
5. **Include the `DPoP` proof header**.

### Step 5 — Parsing the userinfo response

The **format of each attribute is unchanged** from the Person API (same `value` / `code`+`desc` / `source` / `classification` / `lastupdated` structure your parsers already handle). The only difference is that person data is now nested inside a **`person_info`** field, alongside standard OIDC claims.

{% tabs %}
{% tab title="Old (Person API — root level)" %}

```json
{
  "uinfin": { "lastupdated": "2024-09-26", "source": "1", "classification": "C", "value": "S9000001B" },
  "name":   { "lastupdated": "2024-09-26", "source": "1", "classification": "C", "value": "SOH HAO FENG" }
}
```

{% endtab %}

{% tab title="New (Userinfo — nested under person\_info)" %}

```json
{
  "person_info": {
    "uinfin": { "lastupdated": "2024-09-26", "source": "1", "classification": "C", "value": "S9000001B" },
    "name":   { "lastupdated": "2024-09-26", "source": "1", "classification": "C", "value": "SOH HAO FENG" }
  },
  "iss": "https://id.singpass.gov.sg/fapi",
  "sub": "d45d8f21-6178-4713-b962-8635ed2a945a",
  "aud": "T5sM5a53Yaw3URyDEv2y9129CbElCN2F",
  "iat": 1746678089
}
```

{% endtab %}
{% endtabs %}

**Action:** point your existing parsing logic at `response.person_info` instead of the response root. Your downstream mapping should otherwise be reusable.

## What you can decommission after cutover

Once the v5 flow is live and the v3 flow is retired:

* `PKI_SIGN` base-string construction and RSA-SHA256 request-signing code
* X.509 certificate management for API request signing
* `client_secret` storage and rotation for the person flow
* Access-token parsing used to extract `sub` for the Person API path
* Custom scheme URL handling (mobile)
* The v3 app registration itself, after Singpass confirms deactivation

{% hint style="success" %}
**Keep** your JWE/JWS decryption utilities — you will repurpose the concept (with new keys and algorithms) for **ID token decryption**, and your Myinfo Business Entity-Person flow still uses payload decryption per the biz v2 spec.
{% endhint %}

## Migration checklist

**Onboarding**

* [ ] New Myinfo (v5) staging app created (one per purpose)
* [ ] JWKS configured (≥ 1 signing key, ≥ 1 encryption key)
* [ ] HTTPS redirect URIs registered; app-claimed HTTPS set up for mobile
* [ ] Attributes mapped to v5 scopes; allowable scopes confirmed

**Build**

* [ ] Certified OIDC RP library integrated
* [ ] PAR implemented (server-to-server, with DPoP header)
* [ ] PKCE implemented (`code_challenge` / `code_verifier`, S256)
* [ ] `nonce` generation and verification implemented
* [ ] Client assertion (`private_key_jwt`) implemented for the token endpoint
* [ ] ID token decryption + signature verification + claim validation implemented
* [ ] Userinfo call implemented (`Authorization: DPoP`, DPoP proof header, no query params)
* [ ] Response parsing updated to read from `person_info`
* [ ] Old parameters removed: `authmode`, `purpose`, `login_type`, `state` (token request), `client_secret`, `PKI_SIGN`

**Test (staging)**

* [ ] End-to-end happy path with test accounts
* [ ] Negative tests: expired access token (30-min lifetime), invalid DPoP proof, mismatched `nonce`/`state`, replayed authorization code
* [ ] Consent-denied and browser-abandonment paths (verify `state` reconciliation)
* [ ] Mobile: App Links / Universal Links return-to-app verified on both platforms

**Cutover**

* [ ] Production app created and configured
* [ ] Staged rollout plan (run v3 and v5 in parallel where feasible)
* [ ] Monitoring/alerting on new error codes (`invalid_token`, `invalid_dpop_proof`, `invalid_request`)
* [ ] v3 person flow and legacy credentials decommissioned

## Common pitfalls

1. **Reusing the v3 app or credentials** — a new v5 app is mandatory; the old `client_id` will not work.
2. **Sending `purpose` in the request** — it now lives in the portal config.
3. **Comma-separated scopes** — the v5 `scope` is **space-separated** and must include `openid`.
4. **Missing the DPoP header on PAR or userinfo** — DPoP is required on *all three* calls, not just token exchange.
5. **Using `Bearer` on userinfo** — the prefix must be `DPoP`.
6. **Parsing the access token for `sub`** — treat it as opaque; read the UUID from the **ID token**.
7. **Reading person data from the response root** — it is now under `person_info`.
8. **JWKS with only one key** — you need both a **signing** key and an **encryption** key.
9. **Custom scheme redirect URLs** — rejected; mobile apps must use app-claimed HTTPS.

## References

* [Myinfo (v3) apps migration guide](https://docs.developer.singpass.gov.sg/docs/technical-specifications/migration-guides/myinfo-v3-apps)
* [Integration Guide (full v5 flow)](https://docs.developer.singpass.gov.sg/docs/technical-specifications/integration-guide)
* [DPoP guide](https://docs.developer.singpass.gov.sg/docs/technical-specifications/technical-concepts/demonstrating-proof-of-possession-dpop)
* [PKCE guide](https://docs.developer.singpass.gov.sg/docs/technical-specifications/technical-concepts/proof-key-for-code-exchange-pkce)
* [Userinfo request spec](https://docs.developer.singpass.gov.sg/docs/technical-specifications/integration-guide/5.-requesting-for-userinfo)
* [Certified OIDC RP libraries](https://openid.net/developers/certified-openid-connect-implementations/)
* [Myinfo Business v2 spec (entity flow, unchanged)](https://public.cloud.myinfo.gov.sg/myinfobiz/myinfo-biz-specs-v2.0.html)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.developer.singpass.gov.sg/docs/technical-specifications/migration-guides/migrating-mib-v1-v2-person-v3-to-myinfo-v5.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
