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.
0.0.2 release (18 Jul 2024)
0.0.1 release (27 Jun 2024)
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}
{MAJOR}
number introduces incompatible API changes with previous {MAJOR}
number also resets {MINOR}
to 0
,{MINOR}
number introduces new functionalities or information that are backward compatible also resets {PATCH}
to 0
, and{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.
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.The staging enviroment is used for testing your application with the full security measures required in production.
The production enviroment is the actual live environment with full security measures and live data.
The following are the scheduled downtimes for the various environments:
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:
Below is the list of recommended cipher suites that you may use:
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:
Content-Type
header application/json
, alsoContent-Length
header is omitted by having Transfer-Encoding
header chunked
emitted for streaming dataAuthorization uses OAuth2.1 authorization code flow to perform authorization.
The sequence diagram below illustrates the steps involved in integrating your application with our APIs:
The flow consists of 2 APIs:
Authorize
Token
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:
Authentication methods provided by SGFinDex API gateway on internet:
ES256
Client Assertion (see "Client Assertion" section below)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.
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:
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"
}
{typ}
Type - value "JWT"{alg}
Algorithm - value "ES256"{kid}
Key ID - The unique identifier for the key.{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 endpointBelow 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
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;
}
};
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:
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");
}
}
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.
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:
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.
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:
kid
to the original key.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.
For business and technical queries, please contact support@sgdex.gov.sg.
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.
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 |
grant_type required | string Example: |
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:
|
client_assertion_type required | string The format of the assertion as defined by the authorization server. The value will be an absolute URI.
Example: |
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. |
//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; };
{- "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": {
- "client_id": "string",
- "client_name": "string",
- "jku": "string"
}, - "cnf": {
- "jkt": "string"
}, - "encrypted_sub": "string"
}
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.
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. |
{- "keys": [
- {
- "kty": "string",
- "n": "string",
- "e": "string",
- "alg": "string",
- "kid": "string",
- "use": "string",
- "crv": "string",
- "x": "string",
- "y": "string",
- "x5t": "string"
}
]
}