# Custom Integration

You will need to write your own custom sgID integration if your app uses a programming language that **sgID does not have a SDK for**. This page provides a guide for how to implement this custom integration.

When a user tries to log in to your application with sgID, you need to:

1. [Generate a PKCE pair](#step-1-generate-a-pkce-pair)
2. [Create an authorization URL to redirect to](#step-2-create-an-authorization-url-to-redirect-to)
3. [Exchange auth code for access token and ID token](#step-3-exchange-auth-code-for-access-token-and-id-token)
4. [Request for user info with access token](#step-4-request-for-user-info-with-access-token)
5. [Decrypt the user info payload](#step-5-decrypt-the-user-info-payload)

### Step 1: Generate a PKCE pair

Proof Key for Code Exchange (PKCE) is an OAuth 2.0 enhancement and protects against various potential vulnerabilities such as authorization code interception. A unique PKCE pair must be generated for each request and consists of a `code_verifier` and a `code_challenge` .

{% code title="Example PKCE pair" %}

```
code_verifier = 'bbGcObXZC1YGBQZZtZGQH9jsyO1vypqCGqnSU_4TI5S'
code_challenge = 'zaqUHoBV3rnhBF2g0Gkz1qkpEZXHqi2OrPK1DqRi-Lk'
```

{% endcode %}

**Code verifier**

The `code_verifier` should be a high-entropy cryptographic random string with an ABNF as follows

```
ABNF:
code-verifier = 43*128unreserved
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
ALPHA = %x41-5A / %x61-7A
DIGIT = %x30-39
```

**Code challenge**

The `code_challenge` should be generated from the `code_verifier` using the `S256` code challenge method. The `S256` transformation is described below together with the ABNF of the `code_challenge`.

```
S256 Transformation:
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

ABNF:
code-challenge = 43*128unreserved
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
ALPHA = %x41-5A / %x61-7A
DIGIT = %x30-39

// Note that the ABNF for code-challenge is identical to the ABNF for code-verifier
```

{% hint style="info" %}
sgID only supports the `S256` code challenge method as the `plain` method is insecure (see the [PKCE RFC](https://www.rfc-editor.org/rfc/rfc7636)) and only exists for backwards compatibility reasons.
{% endhint %}

The `code_challenge` must be sent to the sgID authorization server when initiating an authorization request, whereas the `code_verifier` must be provided when exchanging the OAuth authorization code for an access token. This allows the sgID server to verify that the server exchanging the access token is the same server that initiated the request!

Here are some cryptography libraries you could use to generate these values:

1. Node.js - [pkce-challenge](https://www.npmjs.com/package/pkce-challenge)
2. Python - [pkce](https://pypi.org/project/pkce/)

### Step 2: Create an authorization URL to redirect to

To allow your user to login into your app with sgID, you need to create an sgID authorization URL.&#x20;

Your app should redirect your user's browser to this authorization URL, which will display a QR code that they can scan to authenticate with the Singpass mobile app:

<pre class="language-javascript" data-title="Example URL"><code class="lang-javascript">https://api.id.gov.sg/v2/oauth/authorize?
    response_type<a data-footnote-ref href="#user-content-fn-1">=</a>code
    &#x26;client_id=abc
    &#x26;redirect_uri=https://example.com/callback
    &#x26;scope=openid%20myinfo.name%20myinfo.passport_expiry_date%20myinfo.nric_number
    &#x26;code_challenge=zaqUHoBV3rnhBF2g0Gkz1qkpEZXHqi2OrPK1DqRi-Lk
    &#x26;nonce=BQO8SV3ALIYA808IZ8O7PKWRI8A8X6MI
    &#x26;state=tk39drykro3
</code></pre>

You will need to supply the following query string parameters:

| Key              | Value                                                                                                                                                                                                                               |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| response\_type   | Must be set to `code` because sgID only supports the authorization code flow                                                                                                                                                        |
| client\_id       | Provided to you during [client registration](https://docs.id.gov.sg/introduction/getting-started/register-your-application)                                                                                                         |
| redirect\_uri    | The callback URL that you provided during [client registration](https://docs.id.gov.sg/introduction/getting-started/register-your-application)                                                                                      |
| scope            | A URL-encoded string of the scopes your client will request for                                                                                                                                                                     |
| code\_challenge  | The code challenge used for PKCE. Used to prevent authorization code interceptions and cross-site request forgery (CSRF)                                                                                                            |
| nonce (optional) | Randomly generated string to be returned in the id\_token. Used to prevent replay attacks. Refer to the [OpenID Connect documentation](https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes) for implementation details |
| state (optional) | A unique and non-guessable value associated with each authentication request about to be initiated                                                                                                                                  |

### Step 3: Exchange auth code for access token and ID token

After the user authenticates with the Singpass mobile app, the user's browser will be redirected back to the callback URL you provided, together with the authorization code and a state value.&#x20;

{% code title="Example callback URL" %}

```
https://example.com/callback?
    code=someAuthCode
    &state=tk39drykro3
```

{% endcode %}

To exchange the `code` for the access token and ID token, make a `POST` request to&#x20;

```javascript
https://api.id.gov.sg/v2/oauth/token
```

with the following request body parameters:

| Key            | Value                                                                                                                                          |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| client\_id     | Provided to you during [client registration](https://docs.id.gov.sg/introduction/getting-started/register-your-application)                    |
| client\_secret | Provided to you during [client registration](https://docs.id.gov.sg/introduction/getting-started/register-your-application)                    |
| code           | The value returned to you as part of the callback URL                                                                                          |
| grant\_type    | Must be set to `authorization_code`                                                                                                            |
| redirect\_uri  | The callback URL that you provided during [client registration](https://docs.id.gov.sg/introduction/getting-started/register-your-application) |
| code\_verifier | A cryptographically random string that was used to generate your code challenge in the authorization request.                                  |

You should receive a response with the following attributes:

| Key           | Value                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| access\_token | Access Token to be used with retrieving the encrypted payload from user info endpoint                                                                                                                                                                                                                                                                                                    |
| id\_token     | <p>JWT token with the associated user claims. Encodes the following: </p><ul><li>iss (hostname)</li><li><strong>sub (end user's unique identifier)</strong> </li><li>aud (client id) </li><li>nonce (only returned if provided in authorization URL) </li><li>exp (seconds before auth request and access token expires) </li><li>iat (timestamp at which id token was issued)</li></ul> |

Example JSON response body:

```javascript
{
    "access_token": "I6zGnxYTy4fZubtb7LcG48K1fHWb5b",
    "id_token": "eyJhbGciOiJ...[truncated]...L6zm6LaWfkBoA",
}
```

{% hint style="info" %}
The ID token is signed with sgID's private key. It is highly recommended that you verify the ID token with our public keys, which can be found [here](https://api.id.gov.sg/.well-known/jwks.json).
{% endhint %}

### Step 4: Request for user info with access token

Once you have the access token, you can use it to request information about the user corresponding to the scopes that you requested. To do so, make a `GET` request to&#x20;

```javascript
https://api.id.gov.sg/v2/oauth/userinfo
```

with the access token you received in the previous step. Example request:

```
GET /v2/oauth/userinfo HTTP/1.1
Host: api.id.gov.sg
Authorization: Bearer I6zGnxYTy4fZubtb7LcG48K1fHWb5b
Content-Length: 57
Content-Type: application/json
```

You should receive a response with the following attributes:

| Key  | Value                                                                                                                                                                                                                                                                                                                                                                                                             |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| sub  | <p>End user's unique identifier for your client - This is the same value as the <code>sub</code> claim in the <code>id\_token</code> returned from the previous response. </p><p></p><p>Note that as part of sgID's privacy-preserving measures, each end user's unique identifier is different for each sgID client</p>                                                                                          |
| key  | An AES-128-GCM symmetric key, or a **block key**, that is encrypted with your client's RSA-2048 public key.                                                                                                                                                                                                                                                                                                       |
| data | <p>JSON object which contains the data you requested in your application scope. To prevent sgID from reading the data, the payload is encrypted with the <strong>block key</strong> referenced in the definition for the <code>key</code> attribute in the same response body. </p><p></p><p>Refer to the <a href="#decrypting-the-payload">following section</a> for instructions on decrypting the payload.</p> |

Example JSON response body:

```javascript
{
    "sub": "abcdef",
    "key": "eyJhbGcDpgYRL4chyXTjgim...[truncated]...Gxa2tO7nghnu-ewD5ZqA",
    "data": {
        // Note: this will contain all the scopes you requested
        "myinfo.nric_number": "eyJlbmMiOiJ...[truncated]...QafqHmGERc3A",
        "myinfo.name": "eyJlbmMiOi...[truncated]...UgJ9hDSTNLVw",
        "myinfo.passport_expiry_date": "eyJlbmMiOi...[truncated]...UvS41pKk9VKQ",
    }
}
```

### Step 5: Decrypt the user info payload

As part of sgID's privacy-preserving measures, user data is transmitted in encrypted form, so that the sgID server is unable to read the data being transacted. The data is encrypted with a **block key**, which itself is encrypted with your client's public key so that only your client has access to the block key.&#x20;

Therefore, to obtain the user data in plaintext, you will need to:

1. Decrypt the `key` received from the user info response with **your client's private key**. This will give you the **block key.**
2. Decrypt the `data` received from the user info response with the **block key** you have just obtained.

![An illustration of how to decrypt the data you received from the user info endpoint](https://2214909052-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpBW92htBuXTrDYoKovQ2%2Fuploads%2FBgmlj8IxHY2bedXsH4DB%2FScreenshot%202022-07-14%20at%209.34.00%20PM.png?alt=media\&token=988d4e14-113b-487c-916c-050cb2a71a9a)

Example decryption:

{% tabs %}
{% tab title="Javascript" %}

```javascript
// We use the node-jose package for working with JWEs and JWKs
// https://github.com/cisco/node-jose
import { JWE, JWK } from 'node-jose'

/**
* Decrypts data into an object of
* plaintext key-value pairs
*
* @param {string} encKey - encrypted block key
* @param {array} block - data
* @param {string} privateKeyPem - private key in pem format
* @returns {object}
*/
async function decryptData(encKey, block, privateKeyPem) {
 const result = {}
 
 // Decrypted encKey to get block key
 const privateKey = await JWK.asKey(privateKeyPem, 'pem')
 const key = await JWE.createDecrypt(privateKey).decrypt(encKey)
 
 // Parse the block key
 const decryptedKey = await JWK.asKey(key.plaintext, 'json')
 
 // Decrypt data
 for (const [key, value] of Object.entries(block)) {
   const { plaintext } = await JWE.createDecrypt(decryptedKey).decrypt(value)
   result[key] = plaintext.toString('ascii')
 }

 return result
}
```

{% endtab %}

{% tab title="Python" %}

```python
from jwcrypto import jwk, jwe

def decrypt_data(self, encrypted_key: str, encrypted_data: dict):
    # Load private_key
    private_key = jwk.JWK.from_pem(self.private_key.encode("utf-8"))
    jwe_key = jwe.JWE()

    # Decrypt encrypted_key to get block_key
    jwe_key.deserialize(encrypted_key, key=private_key)
    block_key_json = jwe_key.payload

    # Load block_key
    block_key = jwk.JWK.from_json(block_key_json.decode("utf-8").replace("'", '"'))
    jwe_data = jwe.JWE()

    # Initialise dict
    data_dict = {}

    for field in encrypted_data:
        # Decrypt encrypted_data[field] to get actual_data
        jwe_data.deserialize(encrypted_data[field], key=block_key)
        data_dict[field] = jwe_data.payload.decode("utf-8")

    return data_dict
```

{% endtab %}
{% endtabs %}

Example of decrypted data:

```javascript
{
  "myinfo.name": "TIMOTHY TAN CHENG GUAN",
  "myinfo.nric_number": "S3000786G",
  "myinfo.passport_expiry_date": "2024-01-01",
}
```

[^1]:
