@govtechsg/ndi-app-check
v1.0.15
Published
NPM module to do app check for SPA
Downloads
1,049
Maintainers
Keywords
Readme
NDI App Check
API Specs
1) NPM install @govtechsg/ndi-app-check
Import the ndi-app-check
npm library
npm install ndi-app-check
2) Initialize the NdiAppCheck
class
Import the ndi-app-check
library into your codebase. ndi-app-check
is am ESModule so it can only be used via import
. However, CommonJS can still import the library via dynamic import syntax shown below.
Typescript (ESM)
import {
NdiAppCheck, // NdiAppCheck class houses the functions to do app check
NdiAppCheckParams, // NdiAppCheck.validateNdiAppCheckHeader function param interface
Options // NdiAppCheck class constructor init param interface
} from "ndi-app-check"
import path from "path"
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// The file path to the Google service account json file
let serviceConfigFilePath = path.join(__dirname, "your-service-config.json")
// Alternatively use a json object instead of Google service account json config file
let serviceConfigJson = {
"type": "service_account",
"project_id": "your project_id",
"private_key_id": "your private_key_id",
"private_key": "<REDACTED>",
"client_email": "play-integrity-verifier@your_project.iam.gserviceaccount.com",
"client_id": "your client_id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/play-integrity-verifier%40your_project.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
let options: Options = {
gcpServiceConfig: serviceConfigFilePath, // the file path string to your Google service account json file
gcpServiceConfigJson: serviceConfigJson, // your service config json object, if this is supplied, gcpServiceConfig can be omitted
iosDeviceCheckKey: "-----BEGIN PRIVATE KEY----- your key -----END PRIVATE KEY-----", // iOS device check private key to call apple deviec ehcek backend api
iosDeviceCheckKeyId: "you device check key id", // iOS device check key id
expiryLimitSeconds: 120, // optional expiry config, default is 120 seconds
inProd: true, // dictates the validation criteria between production and staging environment, allows package name sg.ndi.dev, and also disallow development environment for apple iOS attestation and deviceChecks
debugMode: false // // if true, more console logs will be printed to aid debugging
}
// Initialize NdiAppCheck class
let ndiAppCheck = new NdiAppCheck(options)
Javascript (CommonJS)
(async function() { // CommonJs does not allow top level await
// The file path to the Google service account json file
let serviceConfigFilePath = path.join(__dirname, "your-service-config.json")
let options = {
gcpServiceConfig: serviceConfigFilePath, // the file path string to your Google service account json file
gcpServiceConfigJson: serviceConfigJson, // your service config json object, if this is supplied, gcpServiceConfig can be omitted
iosDeviceCheckKey: "-----BEGIN PRIVATE KEY----- your key -----END PRIVATE KEY-----", // iOs device check private key to call apple deviec ehcek backend api
iosDeviceCheckKeyId: "you device check key id", // iOS device check key id
expiryLimitSeconds: 120, // optional expiry config, default is 120 seconds
inProd: true, // dictates the validation criteria between production and staging environment, allows package name sg.ndi.dev, and also disallow development environment for apple iOS attestation and deviceChecks
debugMode: false // if true, more console logs will be printed to aid debugging
}
let ndiAppCheck;
await import("@govtechsg/ndi-app-check").then( ({ default: defaultImport }) => {
// Initialize NdiAppCheck class
ndiAppCheck = new defaultImport(options)
} );
}) ();
2) Call the validateNdiAppCheckHeader
function in NdiAppCheck
class
NdiAppCheck
class exposes 2 functions.
Typescript (ESM) validateNdiAppCheckHeader
api call
// Determine if this header value requires a public key to validate
let headerNeedsPublicKey = ndiAppCheck.headerNeedsPublicKey(header)
// Fetch the public key from a database or other persistent storage
let pubkey = undefined
if (headerNeedPublicKey) {
/**
* fetch public keys from database
*/
pubkey = <databased fetched publicKey>
}
let appcheck_params: NdiAppCheckParams = {
header: header, //string - header value
nonce: nonce, // string - nonce, hash, challenge (session-token, reg_ref, swk_qr_ref etc)
publicKey: pubKey, // any - publickey only used for Apple key attestation, this value should be obtained from the result of validateNdiAppCheckHeader when a publickey is returned
}
let ndiAppCheckResult = await ndiAppCheck.validateNdiAppCheckHeader(appcheck_params)
Javascript (CommonJS) validateNdiAppCheckHeader
api call
// Determine if this header value requires a public key to validate
let headerNeedsPublicKey = ndiAppCheck.headerNeedsPublicKey(header)
// Fetch the public key from a database or other persistent storage
let pubkey = undefined
if (headerNeedPublicKey) {
/**
* fetch public keys from database
*/
pubkey = <databased fetched publicKey>
}
let appcheck_params = {
header: header, //string - header value
nonce: nonce, // string - nonce, hash, challenge (session-token, reg_ref, swk_qr_ref etc)
publicKey: pubKey, // any - publickey only used for Apple key attestation, this value should be obtained from the result of validateNdiAppCheckHeader when a publickey is returned
}
let ndiAppCheckResult = await ndiAppCheck.validateNdiAppCheckHeader(appcheck_params)
Typescript (ESM) validateNdiAppCheckHeader
response format
// result returned from ndiAppCheck.validateNdiAppCheckHeader conforms to the below interface
declare interface NdiAppCheckResponse {
state: Boolean; // state will be true when validation was successful
reason?: string; // reason will NOT be undefined/null when state == false and validation failed due to security criteria
error?: string; // error will NOT be undefined/null when state == false and validation failed with an exception/error
pubicKey?: any; // pubkey will NOT be undefined/null when state === true and validation succeeded with apple iOS attestation header
}
The NdiAppCheck.validateNdiAppCheckHeader
function will return a result adhering to the interface NdiAppCheckResponse
.
if ndiAppCheckResult
contains a publicKey
property, it means the validateNdiAppCheckHeader
call was made using a Apple iOS key attestation header. This publicKey
is to be saved into a database so that when a validateNdiAppCheckHeader
call made with Apple iOS app assertion header, the assertion header can be validated successfully using the aforementioned publicKey
Accepted Header formats
Android (Google Play)
Header for Google Play Integrity
X-ndi-appcheck: gpi <token>
Android (Huawei)
Header for Huawei SysIntegrity
X-ndi-appcheck: hwsi <token>
Apple iOS
Header for Apple iOS DeviceCheck
X-ndi-appcheck: adc <token>
Header for Apple iOS App Key Attestation
X-ndi-appcheck: aka <token>
Header for Apple iOS App Assertion
X-ndi-appcheck: aaa <token>
Contributing
- Git clone the project from private repository (do contact the author for permissions)
cd SpaAppAttestationModule
- Run
npm i
- As an optional step (if the
node-app-attest
library has not been transpiled i.e.node-app-attest
folder does not exist on the root of this project directory), run
npm run babel:node-app-attest
- Afterwhich, search for the following usages of the
asn1js
andpkijs
library innode-app-attest/src/verifyAttestation.js
and remove theconst asn1 = _asn1js.default.fromBER(clientCertificate.raw); const certificate = new _pkijs.default.Certificate({ schema: asn1.result });
default
property from_asn1js
and_pkijs
. This will result in the following.const asn1 = _asn1js.fromBER(clientCertificate.raw); const certificate = new _pkijs.Certificate({ schema: asn1.result });
- Build the
src
folder by runningnpm run build
npm publish
for publishing the npm package