Skip to content

Signed QR Codes

The signed QR code feature allows users to sign the QR code for onboarding with a private key, so that a malicious person cannot duplicate the QR code and send it to people to scan and onboard to a fake website. When onboarding using a signed QR code, the client code should provide a public key to verify the signature. If the verification fails, which means the QR code may be duplicated, the onboarding process cannot continue.

The SDK provides the API for client code to set the public key and specify whether it requires a signed QR code, an unsigned QR code, or both. By default, the onboarding flow can accept both signed and unsigned QR codes.

When initializing the application, the client code should provide the public key for signed QR code verification.

SDKInitializer.INSTANCE.start(this, services.toArray(new MobileService[0]), "c8b7b172-a33b-46aa-9bf8-ee857166fba3",
         "-----BEGIN PUBLIC KEY-----\n" +
         "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAhGwh29ymDieSc3KxcfEy\n" +
         "9GyxaMZYR9t97s0XAXhq7g9UTZTtaQMj7KDWcKK0nfNcypmrCjMI743QFEgMvJps\n" +
         "sQJRWH4eZcKs/Qpjg9/QX7bqBJW2/5ovuIpS+PJ95SCEJxEHtauIaytqT7KlLMPV\n" +
         "tNe5WnqXv9AZIIk0lJaeY3sPnhjVfsHwVMvGElN6xef3ZJcyf2J8hwxJKu5mEdlo\n" +
         "WHHVhmpt5Oru6psDlfa6dpqUB+BTpwbf8LaHQo3FW48k+W5nET47pgV4yloOG4KM\n" +
         "1JcQsmPY/CiXQrrl/Ktoi6qSW+1qSM1TZLIuTGs497hD/r0F8N7UuYc68RaKTM0n\n" +
         "6Z8ZUGwAetbrcAx3T+W43Hzm9NdR4P3ZYSNIcDo1vKxpH8Hqez6OHk7GrW9nz4wA\n" +
         "rJQVLFyEnU8golAyqQ4GhXRVxojNRwN2v7lLxuGZkxJcwta0vU7tCDhvNQMITjH0\n" +
         "eoVPCDimVyWBnKRQcO9KE1GTt5erwiC19rddWBHSOqxFAgMBAAE=\n" +
         "-----END PUBLIC KEY-----"
);
SDKInitializer.start(this, * services.toTypedArray(), apiKey = "c8b7b172-a33b-46aa-9bf8-ee857166fba3",
        pubKey = "-----BEGIN PUBLIC KEY-----\n" +
                 "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAhGwh29ymDieSc3KxcfEy\n" +
                 "9GyxaMZYR9t97s0XAXhq7g9UTZTtaQMj7KDWcKK0nfNcypmrCjMI743QFEgMvJps\n" +
                 "sQJRWH4eZcKs/Qpjg9/QX7bqBJW2/5ovuIpS+PJ95SCEJxEHtauIaytqT7KlLMPV\n" +
                 "tNe5WnqXv9AZIIk0lJaeY3sPnhjVfsHwVMvGElN6xef3ZJcyf2J8hwxJKu5mEdlo\n" +
                 "WHHVhmpt5Oru6psDlfa6dpqUB+BTpwbf8LaHQo3FW48k+W5nET47pgV4yloOG4KM\n" +
                 "1JcQsmPY/CiXQrrl/Ktoi6qSW+1qSM1TZLIuTGs497hD/r0F8N7UuYc68RaKTM0n\n" +
                 "6Z8ZUGwAetbrcAx3T+W43Hzm9NdR4P3ZYSNIcDo1vKxpH8Hqez6OHk7GrW9nz4wA\n" +
                 "rJQVLFyEnU8golAyqQ4GhXRVxojNRwN2v7lLxuGZkxJcwta0vU7tCDhvNQMITjH0\n" +
                 "eoVPCDimVyWBnKRQcO9KE1GTt5erwiC19rddWBHSOqxFAgMBAAE=\n" +
                 "-----END PUBLIC KEY-----"
)

When starting the onboarding flow, the client code can specify whether the application accepts only signed QR codes, unsigned QR codes, or both, using flow options.

FlowOptions flowOptions =new FlowOptions.Builder()
        .setSignedQRCodeOption(SignedQRCodeOption.SIGNED_ONLY)
        .build();
FlowContext flowContext = new FlowContextBuilder()
        .setApplication(appConfig)
        .setFlowOptions(flowOptions)
        .build();
Flow.start(activity, flowContext);
val flowContext =
        FlowContextBuilder()
        .setApplication(appConfig)
        .setFlowOptions(FlowOptions(
                signedQRCodeOption = SignedQRCodeOption.SIGNED_ONLY
        ))
        .build()
Flow.start(this@WelcomeActivity, flowContext) { requestCode, resultCode, _ ->
    if (resultCode == Activity.RESULT_OK) {
        startMainBusinessActivity()
    }
}

When client code scans a signed QR code to launch an application using the URI Scheme, the SDK provides an API to verify the signature and parse the signed QR code to return the AppConfig instance.

String pubKey = "-----BEGIN PUBLIC KEY-----\n"
                + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n"
                + "4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n"
                + "+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\n"
                + "kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n"
                + "0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\n"
                + "cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\n"
                + "mwIDAQAB\n"
                + "-----END PUBLIC KEY-----";
String signedURI = "https://mobile-tenant1-kevin-signqrcode.cfapps.sap.hana.ondemand.com/mobileservices/deeplinks/signqrcode/config?jws=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbkVuZHBvaW50IjoiaHR0cHM6Ly9tb2JpbGUtdGVuYW50MS1rZXZpbi1zaWducXJjb2RlLmNmYXBwcy5zYXAuaGFuYS5vbmRlbWFuZC5jb20vb2F1dGgyL2FwaS92MS90b2tlbiIsInByb3RvY29sIjoiaHR0cHMiLCJyZXF1aXJlT3RwIjoiZmFsc2UiLCJvYXV0aC4wLmNsaWVudElEIjoiZTQ3MTMyMmMtZjYwZC00NWYzLWE1ODItMzJjM2E5YWU2OTkyIiwib2F1dGguMC5yZWRpcmVjdFVSTCI6Imh0dHBzOi8vbW9iaWxlLXRlbmFudDEta2V2aW4tc2lnbnFyY29kZS5jZmFwcHMuc2FwLmhhbmEub25kZW1hbmQuY29tIiwicG9ydCI6IjQ0MyIsImhvc3QiOiJtb2JpbGUtdGVuYW50MS1rZXZpbi1zaWducXJjb2RlLmNmYXBwcy5zYXAuaGFuYS5vbmRlbWFuZC5jb20iLCJvYXV0aC4wLmdyYW50VHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsIm11bHRpVXNlciI6ImZhbHNlIiwiYXV0aFR5cGUiOiJvYXV0aDIiLCJhdXRob3JpemF0aW9uRW5kcG9pbnQiOiJodHRwczovL21vYmlsZS10ZW5hbnQxLWtldmluLXNpZ25xcmNvZGUuY2ZhcHBzLnNhcC5oYW5hLm9uZGVtYW5kLmNvbS9vYXV0aDIvYXBpL3YxL2F1dGhvcml6ZSJ9.Kgkh2g_hwg_3higkoGDFzYQlQnYXBBjIpJOMYYgY8CcypsUPp2vbmuLxj1MzZU39rV5scstaBub4d_8mEZFudLNrar_wjWv-wFhE-jkZS0Aj9DWVz_B7B4DgNSvkLf3yUyWItWzsIK7Tj4OOXAxzhmk7nwvOK8MNdZc0bU61uIiHbfE4k3Lo7K7VOUNuTQA_ox35oLLzUB0HWBoSJ34Bj1sjDPYZewtgNYL6UqUvZKMaNI4jg2bdetsi7QGcb5SryVLXWoZdnUTyfXFBHb9cZxYaecAG1pNnyeqZVfq3Ef6kbSQBNnOqeu5TWY_0XKQk6jJz_EKnM7-2OpRV1l562w";
AppConfig appConfig = AppConfig.createAppConfigFromSignedString(signedURI, pubKey, null);
val pubKey = "-----BEGIN PUBLIC KEY-----\n" +
         "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n" +
         "4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n" +
         "+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\n" +
         "kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n" +
         "0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\n" +
         "cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\n" +
         "mwIDAQAB\n" +
         "-----END PUBLIC KEY-----"
val signedURI = "https://mobile-tenant1-kevin-signqrcode.cfapps.sap.hana.ondemand.com/mobileservices/deeplinks/signqrcode/config?jws=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbkVuZHBvaW50IjoiaHR0cHM6Ly9tb2JpbGUtdGVuYW50MS1rZXZpbi1zaWducXJjb2RlLmNmYXBwcy5zYXAuaGFuYS5vbmRlbWFuZC5jb20vb2F1dGgyL2FwaS92MS90b2tlbiIsInByb3RvY29sIjoiaHR0cHMiLCJyZXF1aXJlT3RwIjoiZmFsc2UiLCJvYXV0aC4wLmNsaWVudElEIjoiZTQ3MTMyMmMtZjYwZC00NWYzLWE1ODItMzJjM2E5YWU2OTkyIiwib2F1dGguMC5yZWRpcmVjdFVSTCI6Imh0dHBzOi8vbW9iaWxlLXRlbmFudDEta2V2aW4tc2lnbnFyY29kZS5jZmFwcHMuc2FwLmhhbmEub25kZW1hbmQuY29tIiwicG9ydCI6IjQ0MyIsImhvc3QiOiJtb2JpbGUtdGVuYW50MS1rZXZpbi1zaWducXJjb2RlLmNmYXBwcy5zYXAuaGFuYS5vbmRlbWFuZC5jb20iLCJvYXV0aC4wLmdyYW50VHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsIm11bHRpVXNlciI6ImZhbHNlIiwiYXV0aFR5cGUiOiJvYXV0aDIiLCJhdXRob3JpemF0aW9uRW5kcG9pbnQiOiJodHRwczovL21vYmlsZS10ZW5hbnQxLWtldmluLXNpZ25xcmNvZGUuY2ZhcHBzLnNhcC5oYW5hLm9uZGVtYW5kLmNvbS9vYXV0aDIvYXBpL3YxL2F1dGhvcml6ZSJ9.Kgkh2g_hwg_3higkoGDFzYQlQnYXBBjIpJOMYYgY8CcypsUPp2vbmuLxj1MzZU39rV5scstaBub4d_8mEZFudLNrar_wjWv-wFhE-jkZS0Aj9DWVz_B7B4DgNSvkLf3yUyWItWzsIK7Tj4OOXAxzhmk7nwvOK8MNdZc0bU61uIiHbfE4k3Lo7K7VOUNuTQA_ox35oLLzUB0HWBoSJ34Bj1sjDPYZewtgNYL6UqUvZKMaNI4jg2bdetsi7QGcb5SryVLXWoZdnUTyfXFBHb9cZxYaecAG1pNnyeqZVfq3Ef6kbSQBNnOqeu5TWY_0XKQk6jJz_EKnM7-2OpRV1l562w"
val appConfig = AppConfig.createAppConfigFromSignedString(signedURI, pubKey, null)

Last update: May 25, 2022