NAV Navbar
javascript java swift

Introduction

This resource provides all the documentation you'll need to get started developing an application with Trusted Key Relying Party, Issuing Party, or Wallet support.

Onboarding

You will need to get situated with the Trusted Key mobile app, and the developer portal first.

Get the App for your Mobile Device

First, you will need to download the iOS Trusted Key Digital Identity Wallet here or the Android Wallet here. With this, you will be able to signup for a developer account on our developer portal.

Developer Portal

Developers need to have API credentials to access the Trusted Key Platform. The Client ID and Client Secret credentials can be obtained by following the below steps:

  1. If you have not already done so: download the Trusted Key mobile app (instructions in prior section)
  2. Go to the Trusted Key Developer Portal
  3. Click the signup button in top navbar
  4. Enter the email address you used when you registered on the Trusted Key mobile app

Now you can create apps on the apps page of the developer portal. The clientId and clientSecret are the credentials you need to authorize your application on our platform. The sections below will discuss how to create an application on our platform. We offer Relying Party, Issuing Party, and Wallet functionality.

Understanding Recovery

User recovery is a key strength of the Trusted Key platform. When a user installs a new instance of the Trusted Key App, the Trusted Key Platform enables the user to easily recover their Trusted Key Identity token which will allow them to continue using Relying Party services they have previously registered with.

Please note that the Recovery process does not recover any identity information such as email address, driver license, or passport. The user will need to setup this information again in their new instance of their Trusted Key App.

Backup

After intially setting up the Trusted Key App, the user will be prompted to backup their wallet.

Backup

When the user presses the "Backup" button the setup wizard will start. The user will be provided a 12-word list that they are asked to write down. The screen will then randomly choose two words from the list to ensure the user has recorded the list correctly. Finally the user will need to enter a new 6-digit PIN that is specific to the recovery process.

Backup Wordlist Check Wordlist Enter PIN Generate

Recovery

When the user installs a new Trusted Key App the startup screens will provide the option to "Recover Account"

Open

When "Recover Account" is pressed the user will be be asked to enter their 6-digit recovery PIN. They will then be prompted to enter their 12-word recovery wordlist.

Start Empty Words Completed words

Once filled out the Trusted Key Platform will look for a match to the wordlist and complete the recovery process.

Relying Party

OpenID Connect/OAuth 2.0

To configure OAuth client, use this example code:

// Not meant for Android Wallet SDK
// Not meant for iOS Wallet SDK
const ClientOAuth2 = require('client-oauth2')
const client = new ClientOAuth2({
  clientId: ClientId,
  clientSecret: ClientSecret,
  accessTokenUri: 'https://wallet.trustedkey.com/oauth/token',
  authorizationUri: 'https://wallet.trustedkey.com/oauth/authorize',
  redirectUri: 'https://<where your application is hosted>/oauth/callback',
  scopes: ['openid', 'profile', 'email'],
  state: state,
  nonce: nonce
})

The process of integrating Trusted Key Relying Party support is made simple using OAuth and OpenID Connect. OpenID Connect is a simple identity layer on top of the OAuth 2.0 protocol. In technical terms, OpenID Connect specifies a RESTful HTTP API, using JSON as a data format. The standard allows services to easily setup Trusted Key Relying Party support to verify the identity of an end-user, as well as to obtain basic profile information.

Scopes

Scope determines which pieces of data will be returned when calling the UserInfo Endpoint. It's a list of space-delimited scope identifiers. Example: scope=email will let you obtain the user's email address.

Standard Scopes

State

Opaque value used to maintain state between the Authentication request and the callback. Typically, Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a browser cookie.

Nonce

String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token, which is signed by the private key. You must verify that the nonce Claim Value in the ID Token is equal to the value of the nonce parameter sent in the Authentication Request.

Claims

The optional claims Authentication Request parameter requests that specific Claims be returned from the UserInfo Endpoint and/or in the ID Token. It is represented as a JSON object containing lists of Claims being requested from these locations.

Example of how to build claims for authorization endpoint:

// Not meant for Android Wallet SDK
// Not meant for iOS Wallet SDK
/**
 * @param {string} login_hint - query parameter for the user’s registered email and can be passed from a custom form rather than being entered on the Trusted Key OAuth redirect page.
 */
app.get('/exampleLogin/:login_hint?', (req, res) => {
    const claims = {
        "userinfo": {
            "https://auth.trustedkey.com/publicKey": null  // optional claim
        },
        "given_name": null,  // optional claim                       
        "address": {
            "essential": true  // mandatory claim
        }                                               
    }
    req.query.claims = claims

    // this will use the scopes you defined in your OAuth client earily and also the claims defined here
    const redirectUrl = client.code.getUri({query: req.query})

    res.redirect(redirectUrl)
})

Callback Url

Use the Return URL that was registered with the developer portal. The user agent will be redirected to this URL with the authorization code and/or OpenID-Connect id_token.

Use this example function to get the access token and user's info:

// Not meant for Android Wallet SDK
// Not meant for iOS Wallet SDK
// callbackUrl is the url that was entered on the developer portal when you created the app
const rp = require('request-promise-native')

// ... your ExpressJS setup here ...

app.get(callbackUrl, async(req, res) => {
    const error = req.query.error
    const state = req.query.state
    if (error === 'access_denied') 
        return res.status(403).send('Access was denied')
    if (state !== 'login')
        return res.status(500).send('Oops. Could not handle an unknown state')
    try {
        const accessToken = (await client.code.getToken(req.originalUrl)).accessToken
        const res = await rp({
            uri: 'https://wallet.trustedkey.com/oauth/user',
            method: 'GET',
            headers: {'Authorization' : 'Bearer ' + accessToken},
            json: true
        })
        if (!res) 
            return res.status(403).send('profile request failed from wallet service')
        res.json({
            userId: res.sub,
            email:  res.email,  // defined if requested in authorization call via scopes or claims
            gender: res.gender // defined if requested in authorization call via scopes or claims
        })
    } catch (e) {
        console.error(e)
        res.status(500).send('Oops! Something went wrong.')
    }
})

Endpoints

Name Meaning Url
Authorization Identifies the provider authorization endpoint https://wallet.trustedkey.com/oauth/authorize
Token Identifies provider token endpoint which returns the access_token and the ID token id_token https://wallet.trustedkey.com/oauth/token
Invalidate access token Logout https://wallet.trustedkey.com/oauth/logout?accessToken=access_token
UserInfo Fetches user info which was requested and confirmed https://wallet.trustedkey.com/oauth/user (with header 'Authorization' : 'Bearer ' + access_token)

Login

Take a look at right hand side for example login OAuth client

To configure login OAuth client, use this example code:

// Not meant for Android Wallet SDK
// Not meant for iOS Wallet SDK
const ClientOAuth2 = require('client-oauth2')
const uuidv4 = require('uuid/v4')
const client = new ClientOAuth2({
  clientId: ClientId,
  clientSecret: ClientSecret,
  accessTokenUri: 'https://wallet.trustedkey.com/oauth/token',
  authorizationUri: 'https://wallet.trustedkey.com/oauth/authorize',
  redirectUri: 'https://<where your application is hosted>/oauth/callback',
  scopes: ['openid'],
  state: 'login',
  nonce: uuidv4()
})

Signup

Take a look at right hand side for example signup OAuth client

To configure signup OAuth client, use this example code:

// Not meant for Android Wallet SDK
// Not meant for iOS Wallet SDK
const ClientOAuth2 = require('client-oauth2')
const uuidv4 = require('uuid/v4')
const client = new ClientOAuth2({
  clientId: ClientId,
  clientSecret: ClientSecret,
  accessTokenUri: 'https://wallet.trustedkey.com/oauth/token',
  authorizationUri: 'https://wallet.trustedkey.com/oauth/authorize',
  redirectUri: 'https://<where your application is hosted>/oauth/callback',
  scopes: ['openid', 'profile', 'email', 'address', 'phone'],
  state: 'signup',
  nonce: uuidv4()
})

Issuing Party

For issuing party features, please refer to the help guide on the developer portal for instructions on how to request issuing features. You will need to submit an issuing feature request. Once submitted, one of the members of our dev team will generate a certificate for you.

Issuance

Issuing claims allows you to add claims to a user's wallet (signed by your certificate). Once you have requested for the https://auth.trustedkey.com/publicKey claim, you can use it to issue claims to that user.

Example issuance code:

// Not meant for iOS Wallet SDK
// This code example is using our Java SDK (not the Android Wallet SDK)
import java.util.UUID;
import org.json.JSONException;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Date;

import com.trustedkey.IssuerService;
import com.trustedkey.OID;

public class Driver {

    public static void main(String[] args) throws JSONException {

        String clientId = ""; // SET ME: clientId 
        String clientSecret = ""; // SET ME: clientSecret
        String pubkey = ""; // SET ME: public key of end user TK wallet

        IssuerService issuerService = new IssuerService("https://issuer.trustedkey.com/", clientId, clientSecret);

        String requestId = UUID.randomUUID().toString();
        JSONObject attr = new JSONObject();
        attr.put("1.2.3.4.5", "some data"); // using custom OID
        attr.put(OID.EmailAddress, "alvin@tk.com"); // using TK defined OID

        JSONArray images = null;

        issuerService.requestImageClaims(requestId, new Date(), attr, images, pubkey, new CompletionHandler<Void>() {
            @Override
            public void onCompleted(Void result) {
                System.out.println("Success.");
            }
            @Override
            public void onError(String errorMessage) {
                System.out.println(errorMessage);
            }
        });
    }

}
// This code example is using our NodeJS SDK
const OIDs = require('trustedkey-js/oid')
const TokenIssuerService = require("trustedkey-js/services/trustedkeyissuerservice")

const clientId = '' // SET ME: clientId
const clientSecret = '' // SET ME: clientSecret
let pubKey = '' // SET ME: public key of end user TK wallet (can be requested via relying party functionality)

const issuerService = new TokenIssuerService("https://issuer.trustedkey.com", clientId, clientSecret)

let expiryDate = new Date()
expiryDate.setDate(expiryDate.getDate() + (3 * 365)); // expires in 3 years from issuance (required)
let img = require('fs').readFileSync("file.jpg").toString("base64") // this should be base64 encoded data for an image (optional)

// NOTE: you can use oids as keys or you can use the fully qualified namespaced name (see claims.js @ https://github.com/trustedkey/trustedkey.js/blob/master/claims.js)
const attributes = {}
attributes[OIDs.commonName] = 'Full Name' // individual claim to be issued using OID
attributes[OIDs.country] = 'USA' // individual claim to be issued using OID
attributes['given_name'] = 'Bob' // individual claim to be issued using namespaced name from claims.js

issuerService.requestImageClaims({
    'images': [{
        name: "Image Name",
        data: buffer.toString("base64"),
        oid: OIDs.documentImageHead  // issuing an image claim (optional)
    }],
    'loa': 1, // set the level of assurance (optional)
    'attributes': attributes, // claims to issue (required)
    'expiry': expiryDate, // claims expiration date (required)
    'pubkey': pubKey, // public key of end user TK wallet (required)
})

Revocation

As an issuer you may also revoke claims that you issued.

Example code for revocation:

// Not meant for iOS Wallet SDK
// Not meant for Android Wallet SDK
// This code example is using our NodeJS SDK
const IssuerService = require("trustedkey-js/services/trustedkeyissuerservice")
const CredentialRegistryService = require('trustedkey-js/services/credentialregistryservice')
const Utils = require("trustedkey-js/utils")
const uuidv4 = require('uuid/v4')

const clientId = '' // SET ME: clientId
const clientSecret = '' // SET ME: clientSecret
const url = "https://issuer.trustedkey.com"
const issuerService = new IssuerService(url, clientId, clientSecret)
const credentialRegistryService = new CredentialRegistryService(url, clientId, clientSecret)

// in order to revoke you will need to pass in `requestid` to the issuance call, which will later be used for revoke call
const issue = (publicKey, attrs) => {
  let expiry = new Date()
  expiry.setFullYear(expiry.getFullYear() + config.expiryYears)
  return issuerService.requestImageClaims({
    'requestid' : uuidv4(),
    'attributes': attrs,
    'expiry': expiry,
    'pubkey': publicKey,
  })
}

const revoke = async(requestid, serialNo, publicKey) => {
    const pem = (await issuerService.getClaims(requestid, publicKey))[0]
    const serialNo = Utils.parsePem(pem)
    await credentialRegistryService.httpClient.post('revoke', {
        address: serialNo,
        pubkey: publicKey
    })
}

Wallet

Welcome to the Trusted Key Wallet SDK! You can use the Wallet SDK to create an iOS or Android App integrating Digital Identity features.

Core functionality enabled by the Trusted Key Wallet SDK includes:

To create a Wallet, we have SDKs for Swift (iOS) and Java (Android). You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.

Downloading the Trusted Key SDK

The Trusted Key SDK is currently only available to customers with evaluation agreements. To learn more about evaluating the Trusted Key Wallet SDK please contact info@trustedkey.com.

If your company has an evaluation agreement in place please follow these steps to get a copy of the iOS or Android SDK. 1. Register on the Trusted Key Developer portal and create an app. You can just use placeholder info in the App fields such as callback URL. Please see the help guide here for more detailed instructions. 2. Submit a Feature request for your App to become a Wallet. In the Feature Request please specify the a unique Wallet Name. 3. When approved a new button will appear in the left hand bar "SDK". Please click to access and download the iOS and Android Wallet SDKs

SDK Authentication

To authorize, use this code:

import TrustedKeyWalletFramework

class AuthDemo {
  func HttpApi() {
    AppCredential.initialize("api_client_here", "api_secret_here")
    HttpUtils.httpGet(urlPath: "https://wallet.trustedkey.com/api_endpoint_here", onSuccess: { json in }, onError: { error in })
  }
}
import com.trustedkey.trustedkeyframework.Utils;
import com.trustedkey.trustedkeyframework.HttpUtils;

class AuthDemo {
  void HttpApi() {
    AppCredential.initialize("api_client_here", "api_secret_here");
    HttpUtils.httpGet("https://wallet.trustedkey.com/api_endpoint_here", new HttpUtils.HttpHandler() {
      @Override public void onSuccess(JSONObject json) {}
      @Override public void onError(String errorMessage) {}
    });
  }

  void ManualJwt() {
    long exp = 5*60 + System.currentTimeMillis() / 1000L;
    String jose = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
    String claims = "{\"iss\":\"api_client_here\",\"aud\":\"https://wallet.trustedkey.com/api_endpoint_here\",\"exp\":"+exp+"}";
    String jws = Utils.createHmacJws(jose, claims, "api_secret_here");
    String authorizationHeader = "Bearer " + jws;
  }
}
// npm i trustedkey-js
const Http = require('trustedkey-js/services/http');

// Using high-level HTTP API
const http = new Http("https://wallet.trustedkey.com/", "api_client_here", "api_secret_here");
http.get("api_endpoint_here", {param: value})
  .then(data => { })
  .catch(err => { });

// Low-level manual JWT construction
const Utils = require('trustedkey-js/utils');
let jose = {alg:"HS256",typ:"JWT"};
let claims = {iss:"api_client_here",aud:"https://wallet.trustedkey.com/api_endpoint_here",exp:new Date()};
let jws = Utils.createHmacJWS(jose, claims, "api_secret_here");

let httpAuthHeader = {Authorization: "Bearer " + jws};
const Request = require('request');
Request({url:claims.aud, headers: httpAuthHeader}, (err,data) => { });

Trusted Key API uses API credentials to allow access to the API endpoints. You can register your developer account at developer portal.

The API key must be included in all API requests to the server in a header that looks like the following:

Authorization: Bearer hs256_jwt_here

Nested JWT

Make sure to replace api_client_here and api_secret_here with your API credentials.

Pseudo code

let inner_jwt = [
    {"alg":"ES256","typ":"JWT"},
    {"sub":"ec_public_key","aud":"https://wallet.trustedkey.com/api_endpoint_here","exp":time_in_unix_epoch},
    ecdsa_sha256_signature_here
].map(base64url).join('.');

let outer_jwt = [
    {"alg":"HS256","typ":"JWT","cty":"JWT","iss":"api_client_here"},
    inner_jwt,
    hmac_sha256_signature_here,
].map(base64url).join('.');

In addition to the API credentials, some APIs require additional authentication with specific user keypair, in order to authorize the action on behalf of the end user. These APIs use a nested JWT instead, where the payload of the HMAC-SHA256 is itself a Base64-URL encoded JWT with the same payload, signed by the end user using the hardware ES256 key. The HttpUtils class does this for you.

Keypair Management

The core to the user's identity is control of a private key stored within the phones TEE and its associated public key. The user then uses the private key to authorize identity transactions such as login, and establish "ownership" of claims issued to the user. This keypair needs to be generated with the App is first installed and can never leave the TEE, and its validity is confirmed everytime the app restarts.

Example Code:

if !Credential.Default.initialize() {
    // Failed to generate the default credential; already exist, or no Secure Enclave available
}
if (!Credential.Default.initialize()) {
    // Failed to generate the default credential; already exist, or no TrustZone available
}
// The wallet SDK is not supported in javascript.

Generating Keypair

When the app is started for the first time a private and public key is generated to serve as the basis for the user's identity.

Example Code:

if Credential.Default.isValid() {

}
if (Credential.Default.isValid()) {

}
// The wallet SDK is not supported in javascript.

Check for valid keypair

After the private/public key is generated during onboarding the app subsequently checks the validity of the keypair after every startup. It's good to check for the validity of the default set of keypair at application start. The default keypair might get invalidated when the security of the device has been compromised, for example when the phone's lock screen has been disabled.

Example Code: swift Credential.Default.delete()

Credential.Default.delete();
// The wallet SDK is not supported in javascript.

Deleting the Keypair

Some apps alow a user to "deauthorize" the App on their phone. This feature is useful for situations like a user selling their phone. Deleting the keypair removes it from the device's Secure Enclave or TrustZone. Deletion is not the same as revocation: any associated data is still deemed valid by the relying parties, although the user will no longer be able to provide proof of ownership without having the valid keypair.

User Authentication

The Trusted Key Wallet SDK provides an authentication layer for the App. The user can authentication using a 6-digit PIN OR with the phone's native biometrics such as TouchID or FaceID.

Setting 6-digit PIN

Setting PIN Example Code:

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        // App-credentials from developer.trustedkey.com are required for certain blockchain transactions and APIs
        AppCredential.initialize("api_client_here", "api_secret_here")

        // Initialize the Passcode Lock
        PasscodeLockManager.initialize(logoImage: UIImage(named: "passcodelock_logo"))

        if PasscodeLockManager.hasPasscode() {
            // Request the passcode at application start
            PasscodeLockManager.showEnterPasscodeRoot(successCallback: applicationUnlocked)
        }
    }

    func applicationWillEnterForeground(_ application: UIApplication) {

        if (PasscodeLockManager.hasPasscode()) {
            // Request the passcode at application focus
            PasscodeLockManager.showEnterPasscodeRoot(successCallback: applicationUnlocked)
        }
    }

    // ...
}
// To enable the global PIN lock, derive from GlobalState
public final class MyApplication extends PassCodeLock.GlobalState {
    @Override
    public void onCreate() {
        super.onCreate();

        // App-credentials from developer.trustedkey.com are required for certain blockchain transactions and APIs
        AppCredential.initialize("api_client_here", "api_secret_here");

        PasscodeLockManager.initialize(R.mipmap.ic_launcher);
    }
}
// The wallet SDK is not supported in javascript.

Setting PIN Example Code:

class OnboardingSetPinCodeViewController : UIViewController {

    @IBAction func continueButton_Click(_ sender: AnyObject) {
        // Force the user to set a passcode
        PasscodeLockManager.showSetPasscode(parentVC: self, successCallback: {

            // A PIN has been set; continue with on-boarding
        })
    }

    // ...
}
class OnboardingSetPinCodeActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ENABLE = 11234;

    public void clickNext(View v) {
        // Force the user to set a passcode
        PasscodeLockManager.showSetPasscode(SetPinCodeActivity.this, REQUEST_CODE_ENABLE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        // Check which request we're responding to
        if (requestCode == REQUEST_CODE_ENABLE) {

            // A PIN has been set; continue with on-boarding
        }
    }

    // ...
}
// The wallet SDK is not supported in javascript.

During on-boarding, the user is asked to the set a PIN for the Wallet. This PIN is unrelated to the phone's lock screen, since the user might share the phone with others, but can still protect their Wallet.

Changing PIN

Changing PIN Example Code:

@IBAction func changePinCodeButton_Click(_ sender: Any) {

    assert(PasscodeLockManager.hasPasscode());
    PasscodeLockManager.showChangePasscode(parentVC: self, successCallback: { _ in })
}
Preference change_pin = findPreference("change_pin");
change_pin.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
    @Override
    public boolean onPreferenceClick(Preference preference) {
        PasscodeLockManager.showChangePasscode(getActivity());
        return true;
    }
});
// The wallet SDK is not supported in javascript.

Give the user an option to change their PIN.

Registering Identifier

The Trusted Key Wallet SDK supports two types of identifiers; email and mobile#. Typically during the onboarding process the user will be asked to enter an existing email or mobile#. In the case of email, an email is sent to the user with a 6-digit code to verify ownership of the email address. After the code is verified the user will be issued an email claim from the Trusted Key Cloud Identity Services, and the email identifier will be registered and tied to the user's phone devicetoken. In the case of mobile@, a text message is sent to the user with a 6-digit code to verify ownership of the mobile#. After the code is verified the mobile# identifier will be registered and tied to the user's phone devicetoken.

Setting Email as Identifier

Setting Email Example Code:

@IBAction func sendEmailButtonClicked(_ sender: Any) {

    // Grab the email address from the UITextField
    if let email = emailTextField.text {

        // Before requesting email verification, we need keypair to associate the email with
        _ = Credential.Default.initialize()

        // Create a unique request ID for this email verification request
        let uuid = UUID().uuidString

        TrustedKeyIssuerService.shared.requestEmailClaims(requestID: uuid, emailAddress: email, onSuccess: {
                // A verification email has been sent
                // Save the request ID and email address for the verifyEmailClaim call below
                self.requestID = uuid                
            })
        }, onError: { errorMessage in
            // Request for email verification failed; consider deleting the keypair we created
        })
    }
}

@IBAction func verifyCodeClicked(_ sender: Any) {

    // Grab the verification code from the UITextField
    if let code = codeTextField.text {

        // The email had been verified in sendEmailButtonClicked
        let email = emailTextField.text

        TrustedKeyWalletService.shared.verifyEmailClaim(requestID: self.requestID!, authcode: code, emailAddress: email!, onSuccess: {
            //verified email. This email can now be used as a login identifier (username)
        }, onError: { errorMessage in
            // Verification of the code failed
        })
    }
}
public void sendEmailButtonClicked(View view) {

    String email = emailEditText.getText().toString();

    if (!isValidEmail(email)) {
        // reject values that are not valid email addresses
        return;
    }

    // Before requesting email verification, we need keypair to associate the email with
    Credential.Default.initialize()

    // Create a unique request ID for this email verification request
    String uuidString = UUID.randomUUID().toString();

    TrustedKeyIssuerService.shared.requestEmailClaims(
            uuidString,
            email,
            new CompletionHandler<Void>() {
                @Override
                public void onCompleted(Void result) {
                    // Save the request ID and email address for the verifyEmailClaim call below
                    this.requestID = uuid;

                    // A verification email has been sent; enable the verification code text field
                    codeTextField.setEnabled(true);
                    emailTextField.setEnabled(false);
                }

                @Override
                public void onError(String message) {
                }
            }
    );
}

public void verifyCodeClicked(View view) {

    String code = verify_code.getText().toString().trim();

    if (!isValidCode(code)) {
        // reject values that are not valid 6-digit verification codes
        return;
    }

    // The email had been verified in sendEmailButtonClicked
    String email = emailEditText.getText().toString();

    // Verify the code from email using TrustedKeyWalletService
    TrustedKeyWalletService.shared.verifyEmailClaim(this.requestID, code, email, new CompletionHandler<Void>() {
        @Override
        public void onCompleted(Void result) {

            // Before registering with the backend, make sure the user protects the identity with a PIN
            // This will result in a call to onActivityResult with the same value of REQUEST_CODE_ENABLE
            PasscodeLockManager.showSetPasscode(this, REQUEST_CODE_ENABLE);
        }

        @Override
        public void onError(String message) {
            // Verification of the code failed
        }
    });
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    // Check which request we're responding to
    if (requestCode == REQUEST_CODE_ENABLE && resultCode == RESULT_OK) {

        // Now it's safe to register this identity with your backend
        this.registerWithBackend(Credential.Default);
    }
}
// The wallet SDK is not supported in javascript.

During the onboarding process the user will be asked to enter an email address. The email address will then be verified by the Trusted Key Cloud Identity Services backend.

Each request needs a unique requestID, which is easily generated using a new UUID. Before contacting the service, make sure the user has a valid keypair as described in the Keypair Management section, The Trusted Key Issuer Service will then send a verification email to the provided email address. The email contains a unique 6-digit verification code, which the user has to enter to complete the verification. The verification of the code is automatically performed by the Trusted Key backend. This allows the verified email address to be used for future OpenID-Connect requests.

To verify the email, invoked TrustedKeyWalletService.verifyEmailClaim with the same requestID and the email address and verification code that was entered by the user. When verification succeeds, the email address has been registered in the Trusted Key Cloud Identity Services backend.

Requesting claims is an asynchronous process. The actual claims are obtained from the issuer service by a call to ClaimIssuerService.getClaims.

After the email is verified it will need to be registered as the identifier in the process described below.

Setting Mobile# as identifier

The Wallet SDK provides functionaluty to verify a users phone number.

Setting Mobile# Example Code:

let phoneNumber = "..."
// request verification code for a given phone number
 TrustedKeyIssuerService.shared.requestPhoneNumberVerification(phoneNumber: phoneNumber, countryCode:
            "1", via: TrustedKeyIssuerService.Via.sms, onSuccess: {
                // successfully sent verification code via SMS
        }) { err in
            // handle error
        }

        // verify a given phone number
        let phoneNumber = "..."
        let code = code.text // from a text field
            TrustedKeyIssuerService.shared.verifyPhoneNumber(phoneNumber: phoneNumber, countryCode: "1", token: code, onSuccess: {

                // Success. Now, add the phone number to registerLogin (see below)

            }) { err in
               //handle error
            }

// TBD
// This wallet SDK is not supported in javascript.

This is a two-step process. Step 1 we request the verification code SMS. Step 2 we use the code from SMS to verify the given phone number. This only verifys a phone number. After the email is verified it will need to be registered as the identifier in the process described below.

Registering Identifier

Verifying a user's email address, as per the steps above, automatically register that email address as a login name for the user. Any relying party can use the OAuth endpoints to send login requests to the user's wallet using that verified email address. In addition to that, we provide a registerLogin function that can be used to register a custom identifier. This should be a unique verified string value. Ideally this should be a verified phone number or email.

Registering Identifier Example Code:

@IBAction func registerClicked(_ sender: Any) {

    let identifier = "..."

    TrustedKeyWalletService.shared.registerLogin(login: identifier, onSuccess: {
        // Succeeded
    }, onError: { errorMessage in
        // Failed
    })
}
public void registerClicked(View view) {

    String identifier = "...";

    TrustedKeyWalletService.shared.registerLogin(identifier, new CompletionHandler<Void>() {
        @Override
        public void onCompleted(Void result) {
            // Succeeded
        }

        @Override
        public void onError(String message) {
            // Failed
        }
    });
}
// This wallet SDK is not supported in javascript.

The registerLogin function can be used by a Wallet to register any identifier for the user to be used by the OAuth/OpenID-Connect endpoints. This could be, for example, a verified phone number.

Activity Manager

EventManager class manages all activity requests and action (confirm/deny) taken by the user. Below mentioned operations can be performed.

Add Event

Add an event when user Confirm/Deny the request from your wallet app.

  1. Import TrustedKeyFramework iOS SDK
  2. Get RequestInfo object from WalletUtils.SignatureRequest object, user action (confirm or deny), authentication method, Claims shared by the user and the callback url.
    • Claims Shared CSV (comma separated value) → Array of Claim objects that are shared by the user in Confirm/Deny screen. If user action is Deny, claims shared can be passed as empty array [].
    • AuthMethod → Authentication method used by the user in the wallet app
    • RequestResult → Confirm or Deny action performed by the user
  3. Add the event

Example Code:

import TrustedKeyWalletFramework
public static func onConfirmDenyRequest (signatureRequest: WalletUtils.SignatureRequest,
    result: WalletUtils.RequestResult,
    claimsShared: [Claim],
    callback: String?,
    authMethod: AuthMethod?) -> Bool{
    let requestInfo: RequestInfo = RequestInfo()
    requestInfo.requestTime  = signatureRequest.requestTime
    requestInfo.responseTime = Date()
    requestInfo.callbackUrl  = signatureRequest.callbackUrl.absoluteString
    requestInfo.hostName     = signatureRequest.hostName
    requestInfo.loginCode    = signatureRequest.loginCode
    requestInfo.loginHint    = signatureRequest.username ?? ""
    requestInfo.nonce        = signatureRequest.nonce
    requestInfo.actionResult = WalletUtils.RequestResult(rawValue: result.rawValue)!
    requestInfo.objectIDs    = signatureRequest.objectIDs
    requestInfo.callbackType = WalletUtils.CallbackType(rawValue: signatureRequest.callbackType.rawValue)!
    requestInfo.audience     = signatureRequest.audience
    requestInfo.scope        = signatureRequest.scope
    requestInfo.claims       = signatureRequest.claims
    requestInfo.callback     = callback ?? ""
    requestInfo.message      = signatureRequest.message ?? ""
    requestInfo.documentUrl  = signatureRequest.documentUrl?.absoluteString ?? ""
    requestInfo.claimsSharedCSV = ClaimUtils.getClaimsUniqueIDString(claims: claimsShared)
    requestInfo.authMethod   = authMethod

    // Add event to persistent store
    let insertStatus: Bool = EventManager.shared.addEvent(requestInfo)

    return insertStatus
}
import com.trustedkey.trustedkeyframework.AuthMethod;
import com.trustedkey.trustedkeyframework.Claim;
import com.trustedkey.trustedkeyframework.ClaimUtils;
import com.trustedkey.trustedkeyframework.RequestInfo;
import com.trustedkey.trustedkeyframework.TKEnums;
import com.trustedkey.trustedkeyframework.TKEnums.*;
import com.trustedkey.trustedkeyframework.EventManager;
import com.trustedkey.trustedkeyframework.WalletUtils;

public boolean confirmOrDenyRequest(WalletUtils.SignatureRequest loginInfo,
                                  TKEnums.RequestResult result,
                                  ArrayList<Claim> claimsShared,
                                  String callback,
                                  AuthMethod authMethod) {
    RequestInfo requestInfo = new RequestInfo();
    requestInfo.requestTime = loginInfo.requestTime;
    requestInfo.responseTime = new Date();
    requestInfo.callbackUrl = loginInfo.callbackUrl;
    requestInfo.hostName = loginInfo.hostName;
    requestInfo.loginCode = loginInfo.loginCode;
    requestInfo.nonce = loginInfo.nonce;
    requestInfo.actionResult = result;
    requestInfo.objectIDs = loginInfo.objectIDs;
    requestInfo.message = loginInfo.message;
    requestInfo.loginHint = loginInfo.username;
    requestInfo.callbackType = loginInfo.callbackType;
    requestInfo.documentUrl = loginInfo.documentUrl;
    requestInfo.callback = callback;
    requestInfo.audience = loginInfo.audience;
    requestInfo.scope = loginInfo.scope;
    requestInfo.claims = loginInfo.claims;
    requestInfo.claimsSharedCSV = ClaimUtils.getClaimsUniqueIDString(claimsShared);
    requestInfo.authMethod = authMethod;

    boolean insertStatus = EventManager.getInstance(context).addEvent(requestInfo);
    return insertStatus;
}
// This wallet SDK is not supported in javascript.

Get All Events

See right hand side for sample code

Example Code:

// This wallet SDK is not supported in javascript.
internal func getAllEvents(){
    let eventList: [[String: Any]] = EventManager.shared.getPastRequests()
}
import com.trustedkey.trustedkeyframework.EventManager;

private EventManager eventManager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    eventManager = EventManager.getInstance(getContext());
} 

private List<RequestInfo> getAllEvents(){
    List<RequestInfo> eventList = eventManager.getPastRequests();
}

Get Event Details and Claim Details

Get event details and claim information for the given event id.

Example Code:

// This wallet SDK is not supported in javascript.
internal func getEventDetails(_ eventId: String){
    // Fetch event details for the given eventId
  let eventDetailsRequestInfo: RequestInfo = EventManager.shared.getEventDetails(eventId)!
}

internal func getEventClaimDetails(_ eventId: String){
    // Fetch display claims (claim details) for the given eventId
  let displayClaims: [DisplayClaims] = EventManager.shared.getDisplayClaims(eventId)    
}
import com.trustedkey.trustedkeyframework.Claim;
import com.trustedkey.trustedkeyframework.ClaimUtils;
import com.trustedkey.trustedkeyframework.RequestInfo;
import com.trustedkey.trustedkeyframework.EventManager;

private EventManager eventManager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    eventManager = EventManager.getInstance(getContext());
}

private void getEventDetails(String eventId) {
  // Fetch event details for the given eventId
  RequestInfo eventDetailsRequestInfo = EventManager.getInstance(getApplicationContext()).getEventDetails(eventId);
}

private void getEventDetails(String eventId) {
    // Fetch display claims (claim details) for the given eventId
    ArrayList<DisplayClaim> displayClaimArrayList = EventManager.getInstance(getApplicationContext()).getDisplayClaims(eventId);
}

Delete Event

See right hand side for sample code

Example Code:

// This wallet SDK is not supported in javascript.
internal func deleteEvent(_ eventId: String){
    let deleteEventStatus: Bool = EventManager.shared.deleteEvent(eventId)
}
import com.trustedkey.trustedkeyframework.EventManager;

private EventManager eventManager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    eventManager = EventManager.getInstance(getContext());
}

private void deleteEvent(String eventId){
    boolean deleteStatus = eventManager.deleteEvent(eventId);
}

Delete All Events

See right hand side for sample code

Example Code:

// This wallet SDK is not supported in javascript.
internal func deleteAllEvents(){
    let deleteAllEventStatus: Bool = EventManager.shared.deleteAllEvents()
}
import com.trustedkey.trustedkeyframework.EventManager;

private EventManager eventManager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    eventManager = EventManager.getInstance(getContext());
}

private void deleteAllEvents(){
    boolean deleteAllEventStatus = eventManager.deleteAllEvents();
}

Get Events Count

See right hand side for sample code

Example Code:

// This wallet SDK is not supported in javascript.
internal func getEventsCount() -> Int{
    let eventsCount: Int = EventManager.shared.getEventsCount()
    return eventsCount
}
import com.trustedkey.trustedkeyframework.EventManager;

private EventManager eventManager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    eventManager = EventManager.getInstance(getContext());
}

private long getEventsCount(){
    long eventsCount = eventManager.getEventsCount();
    return eventsCount;
}

Identity Claim Management

Request Claims

Request Claims Example Code:

let requestID = UUID().uuidString // To be used to retrive the claims
let expiry = Date()
let attributes : [String:Any] = [
    OIDs.CommonName: "Your Name"
]

// Image claims are optional. You may pass an empty array if you do not want an image claim to be issued
let image : [String:Any] = [
    "name": "diploma.jpg",
    "data": "....base64 encoded JPEG..."
]

TrustedKeyIssuerService.shared.requestImageClaims(
    requestID: requestID,
    expiry: expiry,
    attr: attributes,
    images: [image],
    onSuccess: { },
    onError: { errorMessage in })
String requestID = UUID.randomUUID().toString();
Date expiry = new Date();
JSONObject attributes = new JSONObject();
attributes.put(OIDs.CommonName, "Your Name");

JSONObject image = new JSONObject()
    .put("name", "diploma.jpg")
    .put("data", "....base64 encoded JPEG...");

JSONArray images = new JSONArray();
images.put(image)

TrustedKeyIssuerService.shared.requestImageClaims(
    requestID,
    expiry,
    attributes,
    images,
    new CompletionHandler() {
        public void onSuccess(Void json) { }
        public void onError(String errorMessage) { }
    });
const ClaimIssuerService = require("trustedkey-js/services/claimissuerservice")

// Get the key/secret are from https://developer.trustedkey.com/
const claimIssuerService = new ClaimIssuerService("https://issuer.trustedkey.com", "key", "secret")

let attr = {
    expiry: new Date().toString(),
    attributes: {
        "1.6.43.65.34": "value"
    },
    images: [{
        name: "diploma.jpg",
        data: "....base64 encoded JPEG..."
    }]
}

// You can either use async/await or Promises .then/.catch
const result = await claimIssuerService.requestImageClaims(attr)

Requesting claims is done by contacting the Trusted Key Claim Issuer Service. Before you can issue claims, your app has to be whitelisted by Trusted Key. During the whitelisting procedure we will provision an issuer tenant for your app, including an HSM-backed key for signing all the claims issued to your app.

Requesting claims is an asynchronous process. The actual claims are obtained from the issuer service by a call to TrustedKeyIssuerService.getClaims.

Check claim request status

Claim Request Example Code:

let requestID = self.requestID // Use the same requestID used to request the claims
TrustedKeyIssuerService.shared.getClaims(
    requestID: requestID,
    onSuccess: { claims in },
    onError: { errorMessage in })
TrustedKeyIssuerService.shared.getClaims(requestID, new CompletionHandler<Claim[]>() {
        @Override
        public void onCompleted(Claim[] pemArray) { }

        @Override
        public void onError(String message) { }
    });
// The wallet SDK is not supported in javascript.

After requesting claims, the same requestID should be used to fetch the actual claims once they have been issued by the issuer service.

While the requestClaims-related API relies on the specifics of the issuer, the getClaims API has no such requirement and is identical for all issuer APIs.

Claim status push notification

Claim Status Push Notificatoin Example Code:

class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

        // Register for push notifications
        TrustedKeyWalletService.shared.registerDevice(deviceToken: deviceToken, onSuccess: { _ in }, onError: { _ in })
    }


    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo:[AnyHashable: Any], fetchCompletionHandler:@escaping (UIBackgroundFetchResult) -> Void) {

        // We can receive two kinds of notifications: Signature request and Claim status

        let requestid = userInfo["requestid"] as? String
        if requestid != nil {

            // The claims for request 'requestid' are available; you can use `getClaims` now.
        }
        else {

            // Handle the signature request (see below)
        }
    }

}
public class DeviceTokenService extends FirebaseInstanceIdService {

    @Override
    public void onTokenRefresh() {

        // Grab the unique device token (might get called before user creds are created!)
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();

        TrustedKeyWalletService.shared.registerDevice(refreshedToken, null);
    }

}


public final class NotificationService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        String requestid = remoteMessage.getData().get("requestid");
        if (requestid != null) {

            // The claims for request 'requestid' are available; you can use `getClaims` now.
        }
        else {

            // Handle the signature request (see below)
        }
    }

}
// This wallet SDK is not supported in javascript.

Instead of polling, a Wallet can register for push notifications with the Wallet Service. In order to receive push notifications, your Apple Push Notification certificate and/or your Firbase Cloud Messaging key must have been registered with your App in the Trusted Key Developer Portal.

Identity Transaction Requests

The wallet service manages pending signature requests from relying parties.

Get a pending request

Pending Request Example Code:

TrustedKeyWalletService.shared.getPendingSignatureRequest(onSuccess: { signatureRequest in
  // Handle the request
}, onError: { errorMessage in
  // Handle the error
})
TrustedKeyWalletService.shared.getPendingSignatureRequest(
        new CompletionHandler<WalletUtils.SignatureRequest>() {
            public void onSuccess(WalletUtils.SignatureRequest request) { }
            public void onError(String errorMessage) { }
        }
);
// The wallet SDK is not supported in javascript.

HTTP Response

{
  "nonce": "unique_nonce_for_this_request",
  "callbackUrl": "https://relying_party_callback.url/?may_have=parameters",
  "objectIds": ["optional","requested.information.oid"],
  "documentUrl": "https://optional.document.url/for-signing.pdf"
}

There are three mechanisms for the Wallet App to receive signature requests from relying parties:

This endpoint retrieves the last pending signature request by polling the wallet service. The HTTP Authorization header determines for which user to fetch the request.

We currently have four kinds of signature requests:

The requests are distinguished by the presence or absence of fields in the SignatureRequest object:

Note that document signing might simultaneously involve a request for claims.

HTTP Request

GET https://wallet.trustedkey.com/getPendingRequest

URL Parameters

None. The HTTP Authorization header determines for which user to fetch the request.

Remove a specific signature request

Example Code:

TrustedKeyWalletService.shared.removeSignatureRequest(nonce: signatureRequest!.nonce, onSuccess: { _ in }, onError: { _ in })
TrustedKeyWalletService.shared.removeSignatureRequest(signatureRequest.nonce,
        new CompletionHandler<Void>() {
            public void onSuccess(Void unused) { }
            public void onError(String errorMessage) { }
        });
// The wallet SDK is not supported in javascript.

HTTP Response

{
  "result": true
}

This endpoint removes a specific signature request.

HTTP Request

GET https://wallet.trustedkey.com/removeSignatureRequest/?nonce=...

URL Parameters

Parameter Description
nonce The nonce of the request, as returned by getPendingSignatureRequest

Confirm a information/login request

Example Code:

// Get the requested claims from Wallet
 let claims = ClaimUtils.matchClaims(objectIds: signatureRequest.objectIDs, preferredValue: nil, issuerIDs: [:])

let (derCerts, derIssuers) = ClaimUtils.getClaimsDer(claims: claims)
let base64Certs = ClaimUtils.derToBase64(derCerts)
let base64Issuers = ClaimUtils.derToBase64(derIssuers)

 WalletUtils.invokeClaimCallback(signatureRequest, amr: .pin, claims: base64Certs, issuers: base64Issuers, onSuccess: { json in
            // json could optionally have a session token based on the callback being invoked
        }, onError: { err in
                // handle error
            })
// Create the callback URL for a accepted login
String uri = WalletUtils.buildLoginCallback(loginInfo, true);

// Open the URL with the Android URL handler
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
// The wallet SDK is not supported in javascript.

We will use WalletUtils invokeClaimCallback to confirm a request and send the requested claims.

Deny a information/login request

Example Code:

// Deny the request
  WalletUtils.invokeDenyCallback(signatureRequest, onSuccess: { _ in

            }, onError: { err in
               // handle error
            })
    // TBD
// The wallet SDK is not supported in javascript.

We will use WalletUtils invokeDenyCallback to deny a request.

Push notifications

Example Code:

class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo:[AnyHashable: Any], fetchCompletionHandler:@escaping (UIBackgroundFetchResult) -> Void) {

        // We can receive two kinds of notifications: auth and claim

        let requestid = userInfo["requestid"] as? String
        if requestid == nil {

            let request = WalletUtils.parseLoginNotification(userInfo: userInfo as NSDictionary)

            if (request != nil && isApplicationUnlocked) {

                // Pass the request to a view controller

            } else {

                // Application is locked; save for later
                signatureRequest = request
            }
        }
        else {
            // Handle claim status notification (see above)
        }
    }

}
public final class NotificationService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        String requestid = remoteMessage.getData().get("requestid");

        if (requestid != null) {

            // Handle the claim request (see above)
        }
        else {

            WalletUtils.SignatureRequest signatureRequest = WalletUtils.parseLoginNotification(remoteMessage.getData());
            if (signatureRequest != null) {

                // You can now pass to signatureRequest to an activty by serializing it into a bundle
            }
        }
    }

}
// The wallet SDK is not supported in javascript.

In addition to polling the wallet service for pending requests, the app can register for push notifications and receive signature requests when the app is not running.

Push notification require signing up for either Apple Push Notifications (for iOS) or Firebase Cloud Messaging (for Android).

Example Code:

class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...

    // This gets called when our app is invoked from a Universal Link or some other type of activity.
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        NSLog("AppDelegate - continueUserActivity")

        if (userActivity.activityType != NSUserActivityTypeBrowsingWeb) {
            return false
        }

        if (userActivity.webpageURL == nil) {
            return false
        }

        signatureRequest = WalletUtils.parseLoginUniversalLink(url: webpageURL!)
        return true
    }

}
public class LoginActivity extends AppCompatPinActivity {

    // ...

    @Override
    protected void onResume() {
        super.onResume();

        Uri universalLink = getIntent().getData();
        if (universalLink != null) {

            // Parse
            loginInfo = WalletUtils.parseLoginUniversalLink(universalLink);
        }
        else {

            // Perhaps we got invoked with a bundle?
            loginInfo = WalletUtils.parseLoginBundle(getIntent().getExtras());
        }
    }
}
// The wallet SDK is not supported in javascript.

Finally, an app can register a Universal Link. Clicking this link will open the wallet application, instead of opening the link in the browser. The Wallet Application can then parse the URL parameters and show the signature request details.

First party login

The Wallet SDK can be used to securely login to your Wallet's custom backend.

Request a signature request from the backend

Example Code:

func loginRequest(completion: @escaping (WalletUtils.SignatureRequest?, String?) -> Void) {

    // The 'login' endpoint is exposed by your App's backend to generate a login challenge
    HttpUtils.httpPost("https://your.app.backend/login", json: [:], onSuccess: { json in

        // For registration, force inclusion of the user's public key in the reply
        json.setValue(OIDs.PublicKey, forKey: "objectIds")

        // Parse the JSON structure into a `SignatureRequest` structure
        if let sr = WalletUtils.parseLoginNotification(userInfo: json) {

            completion(sr, nil)
        }
        else {

            completion(nil, LoginService.invalidLoginRequest)
        }
    }, onError: { errorMessage, _ in

        completion(nil, errorMessage)
    })
}
// TO-DO
const Express = require('express')
const Utils = require('trustedkey-js/utils')
const OID = require('trustedkey-js/oid')

const router = module.exports = Express.Router()

/* login request from app. */
router.post('/login', function(req, res, next) {

    const signatureRequest = {
        nonce: Utils.generateNonce(),
        callbackType: "POST",
        callbackUrl: "https://" + req.get('host') + '/confirmLogin',
        objectIds: OID.publicKey,
    }

    // ...store the request nonce in storage...

    res.json(signatureRequest)
})

A login to the App's backend involves first generating a challenge on the backend, and then sending that challenge to the Wallet where the user can confirm or deny the login (or could happen behind the scenes for a seamless experience if this is what you want). Then, the Wallet cryptographically signs the challenge with the user's keypair and sends back the signed challenge to the backend, where it's verified.

Send a signature request to a user

Example Code:

// This wallet SDK is not supported in swift.
// This wallet SDK is not supported in java.
const WalletService = require('trustedkey-js/services/walletservice')

const walletService = new WalletService("https://wallet.trustedkey.com/",
    "api_client_here",
    "api_secret_here")

const nonce = Utils.generateNonce()
walletService.request("user_address_or_login", nonce, "https://your.app.domain/callback_endpoint_here")
    .then(result => {
        console.log(result)
    })
    .catch(err => {
        console.error(err.stack)
        process.exitCode = 2
    })

Your custom backend would need an endpoint that would return a signture request for login. See example CBB implementation.

Respond to the login request

Example Code:

// Sign the request and reply to the backend.
// Setting `result` to `true` will sign the request and invoke the callback URL.
WalletUtils.invokeLoginCallback(loginInfo: signatureRequest!, result: true, onSuccess: { json in

    // At this point you'll want to store any session token from the backend for further API auth
    let token = json["token"]
    UserDefaults.standard.set(token!, forKey: "token")

    completion(nil)

}, onError: { errorMessage in

    completion(errorMessage)
})
// TO-DO
const JsonWebToken = require('jsonwebtoken')

router.post('/confirmLogin', function(req, res, next) {

    // Grab the id_token from the query string
    const id_token = req.query.id_token

    if (id_token === undefined) {
        throw new Error("Unauthorized")
    }

    // Decode the claims first without verification
    let claims = JsonWebToken.decode(id_token)

    // ...verify the id_token...

    // Example of creating a session token and returning it to the client
    const token = Utils.generateNonce()

    // ...store the session token in storage...

    res.json({token: "bearer "+token})
})

The Wallet SDK generates a signed id_token and invokes the callback URL from the signature request with the id_token. The id_token can then be verified on the backend as shown in CBB example.

After verification, the backend should generate a regular bearer token for authentication of any private backend API. In the example we are simply generating a unique nonce and use that as a bearer token on subsequent API calls.

User Controlled Backup/Recovery

Credential backup and recovery

Example Code:

@IBAction func createRecoveryKey_clicked(_ sender: Any) {

    let wordlist = RecoveryKeyUtils.getRandomWordList()

    let recoveryKey = RecoveryKeyUtils.deriveKeyFromWordList(wordList: wordlist, passphrase: nil)

    TrustedKeyRegistryService.shared.registerRecoveryKey(recoveryKey: recoveryKey!, onSuccess: { txid in

        TrustedKeyRegistryService.waitForTransaction(txID: txid, seconds: 30, completion: { txstatus in

            if txstatus == .SUCCEEDED {

                // Save the word list for quick testing of the recovery feature
                UserDefaults.standard.set(wordlist, forKey: "WORDLIST")

                // Show the word list to the user
            }
            else {
                // The transaction timed out or the blockchain could not be contacted
            }
        })

    }, onError: { errorMessage in

        // Recovery failed
    })
}


@IBAction func recover_clicked(_ sender: Any) {

    let wordlist = "..."

    // Derive the key from the saved wordlist (avoid the hassle of entering for purpose of demo)
    let recoveryKey = RecoveryKeyUtils.deriveKeyFromWordList(wordList: wordlist, passphrase: nil)

    // Before invoking the smart contract, we need to create keypair to recover to
    _ = Credential.Default.initialize()

    // Invoke the recovery method on the smart contract; this will return a blockchain transaction ID
    TrustedKeyRegistryService.shared.recoverCredential(recoveryKey: recoveryKey!, onSuccess: { txid in

        // Wait for the transaction to get validated
        TrustedKeyRegistryService.waitForTransaction(txID: txid, seconds: 30) { txstatus in

            if txstatus == .SUCCEEDED {

                // The old identity has been recovered to the newly created one; proceed with email/login
            }
            else {

                // The transaction timed out or the blockchain could not be contacted
            }
        }

    }, onError: { errorMessage in

        // Recovery failed; consider deleting the keypair we created
    })
}
// TO-DO
// This wallet SDK is not supported in javascript.

The user keypair are stored in the phone's secure hardware element and cannot be migrated to other phones. To backup and restore the keypair, a software-backed recovery key can be associated with the keypair and be used at a future time to associate a new set of keypair with the user's identity. This will register the recovery on the blockchain and will mark the old set of keypair as being replaced by the new keypair.

It's customary to have the user create the recovery key from a list of words which the user can write down and then be prompted for at a later time in order to regenerate the same recovery key. In addition to the word list, an optional passphrase can be provided as well, for example a memorized PIN code, to protect against a leaked word list.

Claim backup and recovery

After downloading the claims from the issuer service, a user's claims are only stored on the device and are not kept anywhere in the cloud. The credential recovery procedure mentiond above only takes care of replacing one set of keypair with a new set of keypair on a new phone. Claims will have to be separately backed up, and restored after recovery of the keypair.

The WalletService API helper provides the postBlob and getBlob APIs to store and retrieve arbitrary data on the Trusted Key servers. This data is encrypted and decrypted on the phone, so that our servers never know the contents of what is being stored. By default, the recovery key used by the credential recovery procedure is also used as the encryption key by the blob APIs.

The helpers in the ClaimUtils class can be used to retrieve all claims from the secure device storage and serialize them to a blob for easy backup.

Sample Web App

The sample app offers basic relying party and issuing party functionality, and encapsulates most of the sample code from the reltying party and issuing party sections of this reference.

Auto deployment with Heroku

You can view a barebones sample app here. The code behind this sample app can also be auto deployed from developer portal via the Deploy To Heroku button on your app's detail screen. Otherwise, you can deploy it with this button below.

Follow these 4 easy steps to automatically deploy this sample code to your own heroku app.

Deploy

Sample Wallet App

Sample Wallet App coming soon...

Status Codes

The Trusted Key platform uses standard HTTP response codes for indicating whether a request succeeded or failed. In general, response codes in the 200-level range indicate success, response codes in the 400-level range indicate a failure caused by information provided, and response codes in the 500-level range indicate a failure caused by a problem on the server.

Code Status Meaning
200 Ok API call succeeded
400 Bad Request Your request is invalid
401 Unauthorized Your clientId and/or clientSecret is incorrect
403 Forbidden You are not allowed to use the endpoint
404 Not Found You provided an incorrect endpoint
422 Unprocessable entity The resource you are trying to operate on is invalid
500 Internal Server Error We had a problem handling your request on our platform. Try again later.