SGDex Authorization API (0.0.2-release)

Download OpenAPI specification:Download

Authorization leverages on the OAuth 2.1 for obtaining user consent, issuance and revocation of access_token.

Note: This specification is subject to changes based on evolution of the APIs.

Release Notes

  • 0.0.2 release (18 Jul 2024)

    • Changed state parameter as optional
    • Added client information and confirmation JWK thumbprint to access_token claims
  • 0.0.1 release (27 Jun 2024)

    • Initial Draft

Releases and Compatibility

The RESTful API adopts Semantic Versioning 2.0.0 for releases, and every new release of the API increments the version numbers in the following format:

{MAJOR}.{MINOR}.{PATCH}
  1. {MAJOR} number introduces incompatible API changes with previous {MAJOR} number also resets {MINOR} to 0,
  2. {MINOR} number introduces new functionalities or information that are backward compatible also resets {PATCH} to 0, and
  3. {PATCH} number introduces bug fixes and remains backward compatible.

Pre-release or draft versions, when provided, are denoted by appended hypen - with a series of separate identifiers {LABEL}-{VERSION} following the {PATCH} number. Such releases are unstable and may not provide the intended compatibility with the specification in draft status.

Serving as notice, the RESTful API in version 2.X.X are incompatible with version 1.X.X releases.

Despite backward compatibility in {MINOR} or {PATCH} releases, API consumers are best to evaluate and determine their implementation does not disrupt use-case requirements.

Overview

This API Specification guides you through Authorization integration.

Environments

The RESTful APIs are provided in both testing and live environments, and are accessible over the Internet via HTTPS.

Consumers are to ensure firewall clearance on their edge network nodes for connecting to the APIs.

The convention used by API endpoints' URLs is in the following format:

https://{ENV_DOMAIN_NAME}/{DEX}/{VERSION}/{RESOURCE}
  • {ENV_DOMAIN_NAME} indicates Service Authorization's API domain names - respectively:
  • /{DEX}, indicates the Data Exchange space configured for the API
  • /{VERSION} indicates the endpoint's release {MAJOR} version number path - for this release
  • /{RESOURCE} indicates the API resource path name. Any additional query string parameters are appended as needed.

Available Environments

1. Staging Environment

The staging enviroment is used for testing your application with the full security measures required in production.

Note:

2. Production Environment

The production enviroment is the actual live environment with full security measures and live data.

Note:

Scheduled Downtimes

The following are the scheduled downtimes for the various environments:

Production Environment

  • Monthly 8 hours scheduled downtime.
  • Downtime will be announced prior to date.
  • Communication by email

Staging Environment

  • None

Security

HTTPS Interface

Authorization API gateway supports accessing of APIs via the following interfaces:

  • HTTP version 1.1 connection over TLS (Transport Layer Security) version 1.2 standards, and ciphersuites:

    • using AES (Advanced Encryption Standard) and SHA (Secure Hash Algorithm),
    • on either GCM (Galois/Counter Mode) or CBC (Cipher Block Chaining) mode.
  • Below is the list of recommended cipher suites that you may use:

    • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

IMPORTANT: ensure your server supports TLS 1.2 and supports a cipher suite in the list above.

Accessing the RESTful APIs using prior versions of TLS and/or unsupported ciphersuites will result in connectivity errors. SGFinDex API gateway does not support 2-way TLS client nor mutual authentication.

API HTTP interface features:

  1. JSON (JavaScript Object Notation) is the supported data media format and indicated in Content-Type header application/json, also
  2. Content-Length header is omitted by having Transfer-Encoding header chunked emitted for streaming data

OAuth2.1

Authorization uses OAuth2.1 authorization code flow to perform authorization.

The sequence diagram below illustrates the steps involved in integrating your application with our APIs:

OAuth

The flow consists of 2 APIs:

  1. Authorize

    • This will trigger the login and consent page. Once successful, your application will receive the authorization code via your callback url.
  2. Token

    • Call this server-to-server API with a valid authorization code to get the access token.

Application Authentication

Access to all server-to-server APIs will be authenticated by Authorization API gateway. Prior to calling the APIs, respective consumers are required to have:

  • approval of access, onboarding process for the required API resources will be provisioned, and
  • authentication credentials are then supplied and exchanged.

Authentication methods provided by SGFinDex API gateway on internet:

  • OAuth 2.1 using ES256 Client Assertion (see "Client Assertion" section below)

Proof Key for Code Exchange (PKCE)

PKCE is an extension to the Authorization Code flow to prevent CSRF and authorization code injection attacks (Refer to https://datatracker.ietf.org/doc/html/rfc7636). The mechanism relies on 2 parameters namely code_challenge (base64(sha256(code_verifier))) and code_verifier (cryptograhic random string generated and kept secret on server side) sent in the authorize and token call respectively to enable the Authorization Server to perform correlation. Below is an example of how code_verifier and code_challenge are produced on server side. The code_challenge can then be attached to the authorize call.

Sample Code in NodeJS

  function base64URLEncode(str) {
    return str.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
  }
  function sha256(buffer) {
    return crypto.createHash('sha256').update(buffer).digest();
  }
  function generateCodeChallenge(codeVerifier) {
    try {
      let codeChallenge = base64URLEncode(sha256(this.codeVerifier));
      return codeChallenge;
    } catch (error) {
      // error handling
      throw (error);
    }
  }
  var codeVerifier = base64URLEncode(crypto.randomBytes(32)); 
  var codeChallenge = generateCodeChallenge(codeVerifier);

Authentication methods provided by Myinfo on internet:

  • OAuth 2.1 using Client Assertion
  • Client Assertion should be signed using a key that is published on partner's JWKS endpoint, which is submitted during client onboarding.

Client Assertion

The Partner's application is required to generate client assertions to be attached to server-to-server calls to prove authenticity (Refer to https://datatracker.ietf.org/doc/html/rfc7521). The partner's private key signs the assertion metadata, and SGFinDex will use the partner's onboarded JWKS endpoint to obtain the public key for verification. Below is a sample of the JWT header and payload of the client assertion:

  {
    "typ": "JWT",
    "alg": "ES256",
    "kid": "x0zDLIC9yNRIXu3gW8nTQDOMNe7sKMAjQnZj3AWTW2U",
  } . {
    "sub": "STG2-APIM-SELF-TEST",
    "jti": "jNDZuyLw66gkTjmCNMawzrTJNlhS8wdjpU0DHTzo",
    "aud": "https://auth.sgdex.gov.sg/v1/token",
    "iss": "STG2-APIM-SELF-TEST",
    "iat": 1662365106,
    "exp": 1662365406,
    "htm": "POST"
  }

Description of JWT header attributes:

  • {typ} Type - value "JWT"
  • {alg} Algorithm - value "ES256"
  • {kid} Key ID - The unique identifier for the key.

Description of JWT payload attributes:

  • {sub} Subject - client_id issued by Myinfo upon onboarding
  • {jti} JWT ID - random unique identifier
  • {aud} Audience - URL that partner's application is calling
  • {iss} Issuer - client_id issued by Myinfo upon onboarding
  • {iat} Issued At (Payload) - current timestamp
  • {exp} Expiry (Payload) - expiry timestamp
  • {htm} HTTP method of endpoint

Sample post body with client assertion

Below is an example of a post body of a token API call:

  // Response body of token call in a transaction

  POST /token HTTP/1.1
  Host: stg.fpdsapim.myinfo.gov.sg
  Content-Type: application/x-www-form-urlencoded

  grant_type=authorization_code&
  code=5uHGo2QpNAG99gO6rjqtzNuzrdnzJwatjpeuejvB&
  redirect_uri=http%3A%2F%2Fmyapp.com%2Fcallback&
  code_verifier=ZK2mVhN9gBY9aGytuTIYmU7yHMueb7jZxMrE4WzjRRU&
  client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&
  client_assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImFRUHlaNzJOTTA0M0U0S0Vpb2FIV3ppeHQwb3dWOTlnQzlrUkszODhXb1EifQ.eyJzdWIiOiJTVEcyLU1ZSU5GTy1TRUxGLVRFU1QiLCJqdGkiOiJqTkRadXlMdzY2Z2tUam1DTk1hd3pyVEpObGhTOHdkanBVMERIVHpvIiwiYXVkIjoiaHR0cHM6Ly9zaXQuYXBpLm15aW5mby5nb3Yuc2cvY29tL3Y0L3Rva2VuIiwiaXNzIjoiU1RHMi1NWUlORk8tU0VMRi1URVNUIiwiaWF0IjoxNjYyMzY1MTA2LCJleHAiOjE2NjIzNjU0MDYsImNuZiI6eyJqa3QiOiJHX3E4UXY5LXh2Xzl4Sm8tZXNvbFRudnhWU29iTUVSN08wTEtHUEJsVHFZIn19.OuXR4qOmY8Lilqf6QcgC7PW1hRAgWoG41gHdSC4N-6UiipH1kgdXynazq0rF5JxqOi0Bxah4Jh41KbgEoJ3geQ&
  state=state

Sample Code in NodeJS that generates the client assertion

  async function generateClientAssertion(url, clientId, privateSigningKey) {
    try {
      let now = Math.floor((Date.now() / 1000));

      let payload = {
        "sub": clientId,
        "jti": generateRandomString(40),
        "aud": url,
        "iss": clientId,
        "iat": now,
        "exp": now + 300,
        "htm" : "POST"
      };

      let jwsKey = await jose.JWK.asKey(privateSigningKey, "pem");
      let jwtToken = await jose.JWS.createSign({ "format": 'compact', "fields": { "typ": 'JWT' } }, jwsKey).update(JSON.stringify(payload)).final();
      // "typ" attribute in header generated with option specified. kid and alg attributes are generated automatically.
      logger.info("jwtToken", jwtToken);
      return jwtToken;
    } catch (error) {
      logger.error("generateClientAssertion error", error);
      throw constant.ERROR_GENERATE_CLIENT_ASSERTION;
    }
  };

Token Validation

Access Tokens are in JWT format. This JWT complies to the standard 'JSON Web Token (JWT) Profile for OAuth 2.1 Client Authentication and Authorization Grants'.

You will need to verify the token with our public key from following JWKS:

Sample Code in NodeJS

  // Sample Code for Verifying & Decoding JWS or JWT
  function verifyJWS(jws, publicCert) {
    // verify token
    // ignore notbefore check because it gives errors sometimes if the call is too fast.
    try {
      var jwspayload = jwt.verify(jws, fs.readFileSync(publicCert, 'utf8'), {
        algorithms: ['ES256'],
        ignoreNotBefore: true
      });
      return jwspayload;
    }
    catch(error) {
      throw("Error with verifying and decoding JWS");
    }
  }

JWKS Requirements for Consumer

When onboarding to SGDex, every Consumer is required to provide a JWKS (JSON Web Key Set) endpoint.

The JWKS endpoint which hosts the JWK (JSON Web Key) must meet the following requirements.

  • Endpoint is served behind HTTPS on port 443 using a TLS server certificate issued by a standard publicly verifiable CA issuer (no private CAs), with a complete certificate chain presented by the server.
  • No other custom HTTP header requirements outside standard HTTP headers.
  • Able to respond in 3 seconds.

A JWKS endpoint can host multiple JWKs, using a key ID kid to distinguish between each JWK.

The JWK will be used in the following scenarios:

  • Signature JWK is used to verify the client assertion presented when consuming APIs.

JWK for Signature

The signature JWK will be used to verify the client assertion JWT presented in request, thereby authenticating the client.

The signature JWK should have the following attributes:

  • Must contain a key use use field of value sig, refer to rfc7517#section-4.2.

  • Must contain a key ID kid field, refer to rfc7517#section-4.5.

  • Must contain key type kty of EC.

    • For key type kty of value EC, with algorithm alg of value ES256 and curve crv of value P-256 (For version 2 APIs).

Example:

// EC Signature Key
{
  "kty": "EC",
  "use": "sig",
  "kid": "sig-2021-01-15T12:09:06Z",
  "alg": "ES256",
  "crv": "P-256",
  "x": "Tjm2thouQXSUJSrKDyMfVGe6ZQRWqCr0UgeSbNKiNi8",
  "y": "8BuGGu519a5xczbArHq1_iVJjGGBSlV5m_FGBJmiFtE"
}

Key rotation for signature key

To rotate the signature keys with zero downtime, the client must:

  • Host the replacement signature key in the JWKS endpoint onboarded.
  • Ensure the replacement signature key has a different key ID kid to the original key.
  • Ensure the replacement signature key matches the other cryptographic key requirements.

Error Handling

The RESTful APIs used HTTP specification standard status codes to indicate the success or failure of each request. For HTTP 400 Bad Request errors, the response content will be in the following JSON format:

{
    "code": "integer (int32)",
    "message": "string"
}

Refer to the individual API definitions for the error codes you might encounter for each API.

Support

For business and technical queries, please contact support@sgdex.gov.sg.

Authorization

Authorize

This API triggers Singpass login and consent to retrieve user's data from SGDex. Once the user has authenticated and consented, an authorization code (authcode) will be returned for verification via the callback URL defined. The authcode can then be used to retrieve an access token via the Token API.

Note:

  • This API is public and should be implemented as a link or button on your online webpage.
query Parameters
scope
required
string

Example: cpfbalances noa hdbownership.outstandingloanbalance hdbownership.monthlyloaninstalment
Space separated list of scopes requested.

redirect_uri
required
string

Your callback URL to return to with the authorization code.

client_id
required
string

Unique ID for your application.

response_type
required
string
Default: "code"

Response type for authorization code flow - must be "code". If not provided, the system will default the value to code.

code_challenge
required
string

The Base64-URL encoded value of the S256 hash of the ASCII encoding of code_verifier.

code_challenge_method
required
string

The PKCE code challenge method that is being used. The value 'S256' is required.

state
string

Identifier to reconcile query and response. This will be sent back to you via the callback URL. Use a unique system generated number for each and every call.

app_launch_url
string

Intent URL or universal link of Mobile Application for Singpass Mobile to launch after authentication process.

purpose
required
string

State the purpose for requesting the data. This will be shown to users for their consent.

Responses

Request samples

function callAuthorizeApi() {
  var authorizeUrl = authApiUrl + "?client_id=" + clientId +
    "&scope=" + attributes +
    "&response_type" + responseType +
    "&code_challenge=" + codeChallenge +
    "&code_challenge_method=" + "S256" +
    "&redirect_uri=" + redirectUrl;

  window.location = authorizeUrl;
}

Token

This API generates an access token when presented with a valid authcode obtained from the Authorise API. This token can then be used to request for the user's data that were consented.

Request Body schema: application/x-www-form-urlencoded
code
required
string

The authcode given by the Authorise API endpoint response.

redirect_uri
required
string

Application's callback URL. This value has to be similar to the redirect_uri parameter provided in Authorise call.

grant_type
required
string

Example: authorization_code
The grant type that the application is accessing.

client_assertion
required
string

The assertion being used to authenticate the client, please refer to Client Assertion for details. This JWT is signed with consumer private key and contains the following parameters:

  • sub: Subject - client_id issued by SGDex upon onboarding
  • jti: JWT ID - random unique number
  • aud: Audience - URL of token endpoint
  • iss: Issuer - client_id issued by SGDex upon onboarding
  • iat: Issued At - current timestamp, maximum 300 seconds ago ( 5 minutes)
  • exp: Expiry - expiry timestamp, maximum 300 seconds (5 minutes)
  • htm: HTTP method
client_assertion_type
required
string

The format of the assertion as defined by the authorization server. The value will be an absolute URI. Example: urn:ietf:params:oauth:client-assertion-type:jwt-bearer

code_verifier
required
string

High-entropy cryptographic random STRING using the unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" from Section 2.3 of [RFC3986], with a minimum length of 43 characters and a maximum length of 128 characters.

state
string

Identifier to reconcile query and response. Use a unique system generated number for each and every call.

Responses

Request samples

//get access token using authcode
async function getAccessToken(authCode, privateSigningKey, codeVerifier, sessionPopKeyPair) {
try {

  let tokenUrl = process.env.MYINFO_API_TOKEN_URL;
  let redirectUrl = process.env.PARTNER_REDIRECT_URL;
  let clientId = process.env.MYINFO_APP_CLIENT_ID;

  let cacheCtl = "no-cache";
  let contentType = "application/x-www-form-urlencoded";
  let method = constant.HTTP_METHOD.POST;
  let clientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
  let clientAssertion = await generateClientAssertion(tokenUrl, clientId, privateSigningKey);
  // assemble params for Token API
  let strParams = `grant_type=authorization_code` +
      "&code=" + authCode +
      "&redirect_uri=" + redirectUrl +
      "&code_verifier=" + codeVerifier +
      "&client_assertion_type=" + clientAssertionType +
      "&client_assertion=" + clientAssertion
      "&state=" + state +

  let params = querystring.parse(strParams);

  // assemble headers for Token API
  let strHeaders = `Content-Type=${contentType}&Cache-Control=${cacheCtl}}`;
  let headers = querystring.parse(strHeaders);

  // invoke Token API
  let parsedTokenUrl = urlParser.parse(url);
  let tokenDomain = parsedTokenUrl.hostname;
  let tokenRequestPath = parsedTokenUrl.path;

  return getHttpsResponse(hostname, path, headers, method, params);
} catch (error) {
    throw error;
};

Response samples

Content type
application/json
{
  • "sub": "string",
  • "jti": "string",
  • "scope": "string",
  • "token_name": "access_token",
  • "token_type": "Bearer",
  • "grant_type": "authorization_code",
  • "expires_in": "string",
  • "aud": "string",
  • "iss": "string",
  • "iat": "string",
  • "nbf": "string",
  • "exp": "string",
  • "client": {
    },
  • "cnf": {
    },
  • "encrypted_sub": "string"
}

Retrieving JSON Web Key Set (JWKS)

Get Public Key

This API will return the updated SGFinDex's public key information by default. With passing kid (the unique identifier for the key) in the path parameter, this API will return the specified public key information.

path Parameters
kid
string

The unique identifier for the key.

NOTE: If this parameter is not provided, the result will show an array of keys that are available.

Responses

Response samples

Content type
application/json
Example
{
  • "keys": [
    ]
}