Android Integration Guide

This guide is for developers integrating ZenKey into their Android applications.

1.0 Background

ZenKey is a secure bridge between your users and the apps and services you provide. As the joint undertaking of the four major US wireless carriers — AT&T, Sprint, Verizon, and T-Mobile — ZenKey leverages encryption technologies in a user's mobile phone and mobile network. The platform packages multiple factors of authentication into a streamlined experience for app and website providers, taking advantage of the unique capabilities and insights of the wireless carriers. It then applies those capabilities to provide an easy and secure way to register, login, and perform other types of authorizations within apps and services. The result for you is a better user experience and a more secure link to users.

1.1 OpenID Connect

ZenKey makes integration easy by following the OpenID Connect (OIDC) authentication protocol. OpenID Connect is based on the OAuth 2.0 specification. It uses JSON Web Tokens (JWTs) obtained using OAuth 2.0 flows. The ZenKey SDK uses OIDC to support developers creating experiences in web and native applications. You can read more about OIDC here.

1.2 Authorization Flow on a Primary Device

Before a user can sign into your app using ZenKey, they must first install their carrier’s version of the ZenKey app on their primary device. After completing a simple initial setup, they may now use ZenKey to log in to third-party applications. Pressing the ZenKey button in a third-party app or website from their primary device starts the authentication process, prompting the user to set up their device up as a primary device if they have yet to do so. To learn more about what the authorization flow looks like in greater detail, please see Section 3.3: Authorization User Experience in our guide Enrolling and Managing Users.

Note: This primary device is also the device with which users can authenticate requests from other devices, such as desktops and tablets (see Section 1.3 Authorization on Secondary Devices).

Primary Flow

Step 1:   The user's wireless carrier is determined by the mccmnc — a six-digit number representing the user’s mobile country code (mcc) and mobile network code (mnc) — whose value allows for SIM and user authentication. The carrier returns an authorization code via your Redirect URI.

Step 2:   Your app or website makes an authorization code request to the local ZenKey app.

Step 3:   If the user consents to share information, your backend server issues a token request for user info and other resources.

1.3 Authorization Flow on Secondary Devices

Users can also authenticate with ZenKey on devices other than their primary device, such as a tablet or laptop. These secondary devices rely on the user to complete the authentication process.

Users pressing the ZenKey button on a secondary device will see a visual and numeric code as a part of the secondary device authorization process. This code allows the user to associate that secondary device with their primary device.

Secondary Device Flow

Step 1:   The SDK presents the carrier Discovery UI. If the user is authorizing a secondary device from an app on a tablet, the SDK will use a webview for this step.

Step 2:   The user then scans the visual code or enters the numeric code into the ZenKey app on their primary device.

Step 3:   Once the user approves the request in the ZenKey app on their primary device, the carrier Discovery UI returns a login_hint_token to your app’s Redirect URI. This login_hint_token is only returned to a secondary device for use during the authentication request.

Step 4:   To perform SIM and user authentication, your app makes an authorization code request to the appropriate carrier and receives the auth code in its Redirect URI.

Step 5:   If the user consents to share information, your backend server issues a token request for user info and other resources.

1.4 User Data

To create a secure experience, user info and attributes are only shared via a request from your secure backend to the secure backend of your user's carrier. User information is also only shared with you upon consent. Users are free to choose whether to share their data and specifically what data to share with you.

2.0 Getting Started

Before integrating the ZenKey SDK in your application, there are a few things you should do:

  • Login to the Service Provider Portal.

  • Register your application and obtain a valid client_id and client_secret.

  • Consider whether you will need a custom Redirect URI. When a user authorizes your application, the ZenKey SDK will send an authorization code request to the appropriate carrier, then redirect the user back to the app via your Redirect URI. Your Redirect URI may also be used for callbacks to several ZenKey services. When you configure your Redirect URI in the Service Provider Portal, its scheme must resemble one of the following formats:

    • {client_id}://com.xci.provider.sdk
    • https://URL/URI
    • {custom}://
    • com.sp.app://
  • Identify which scopes, or user attributes, to capture when a user authorizes your app. During enrollment, users provide the ZenKey SDK with basic personal information (e.g. name, email, address) while other scopes originate from the carrier without user input (e.g. phone number). When users sign into your app with ZenKey, they can then elect to share that data with you. Besides the mandatory .openid scope, all other scopes are optional.

2.1 ZenKey SDK Dependencies

The ZenKey SDK has the following dependency.

"com.android.support:customtabs:27.1.1"

If your application is using AndroidX, you will have to Jetify this dependency. For more information about migrating to AndroidX, see here.

If your application is using another version of the support library, you can force usage of your version. For more information about forcing dependency version, see here.

3.0 Add the ZenKey SDK

The ZenKey SDK is provided as a Gradle, Maven and Ivy reference. To use Gradle to integrate ZenKey in your project, first add the JCenter repository to your Android project if you do not already have it.

allprojects {
    repositories {
        jcenter()
    }
}

Next, add the ZenKey SDK as a dependency in your application module.

dependencies {
    implementation 'com.xci.android:zenkey-sdk:0.x.y'
}

Note: Make sure to replace the x and y above with the latest version numbers, which you can find here.

4.0 Add the Internet Permission

If you don't already have the internet permission, add it to your Android manifest.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <application>
        [...]
    </application>
</manifest>

5.0 Configure your Client ID

The ZenKey SDK manifest contains a placeholder for your clientId. In order to compile your app with the ZenKey SDK, you must register the clientId placeholder value in your app build.gradle. Replace MY_CLIENT_ID by the value obtained in the previous step.

android {
    defaultConfig {
        manifestPlaceholders = [zenKeyClientId: 'MY_CLIENT_ID']
    }
}

6.0 Request Authorization Code

Authorization requests are issued by starting an authorization intent which the ZenKey app receives. To trigger an authorization request, use the ZenKey default button which handles the interaction with the identityProvider and starts the authorization intent. To do this, you will need to add ZenKeyButton to your layout or get the authorization intent from identityProvider.

6.1 Add ZenKeyButton Inside Layout

The ZenKey SDK provides a default button to handle the interaction with the identityProvider and start the authorization intent. To use the ZenKey button, add the following to your XML layout.

         <com.xci.zenkey.sdk.widget.ZenKeyButton
             android:id="@+id/zenKeyButton"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"/>
6.1.2 Dark Button

You can customize the appearance of the button. A dark button style is appropriate to use with light backgrounds. By default, the ZenKey SDK uses the dark style. The dark button style looks like this:

ZenKey Button Dark
6.1.3 Light Button

The light button style is appropriate to use with dark backgrounds. To use the light button style, add the parameter app:ZenKeyButtonMode="LIGHT" as follows:

        <com.xci.zenkey.sdk.widget.ZenKeyButton
              android:id="@+id/zenKeyButton"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              app:ZenKeyButtonMode="LIGHT"/>

The light button style looks like this:

ZenKey Button Light
6.1.4 Button Text

You may also set the text of your button using the parameter app:ZenKeyButtonText. For example:

       <com.xci.zenkey.sdk.widget.ZenKeyButton
            android:id="@+id/zenkeyButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            app:ZenKeyButtonMode="DARK"
            app:ZenKeyButtonText="CONTINUE"/>
6.1.5 Set Fragment (if used inside fragment)

If you use the ZenKey Button inside a fragment, you must set this fragment to the ZenKeyButton in order to receive the result inside the fragment onActivityResult() method. Otherwise, the result will be received in the onActivityResult() method of the host activity.

public class MyFragment extends Fragment {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_layout);
        findViewById(R.id.myZenKeyButton).setFragment(this);
    }
}
6.1.6 Set Scopes (optional)

By default, authorization requests made with the ZenKey SDK include the scope OpenId. For additional scopes using the ZenKeyButton, you set them as follows:

public class MyActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_layout);
        findViewById(R.id.myZenKeyButton).setScopes(Scopes.EMAIL, Scopes.fromValue("Any"));
    }
}
6.1.7 Set Custom Request Code (optional)

Optionally, you can set the request code as follows:

public class MyActivity extends AppCompatActivity {

    private int MY_REQUEST_CODE = 9;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_layout);
        findViewById(R.id.myZenKeyButton).setRequestCode(MY_REQUEST_CODE);
    }
}

6.2 Using IdentityProvider

Instead of the default ZenKeyButton, you can also invoke ZenKey with your own custom button or view. You may choose this option if, for example, your login UX is presented in a WebView (see Section 6.3: Using a WebView).

You can make authorization requests using identityProvider, but we recommend using ZenKeyButton (the ZenKey default button that handles the interaction with the identityProvider and starts the authorization intent). However, if you use identityProvider to obtain the authorization intent, you would call ZenKey.identityProvider().authorizeIntent().build().

The intent obtained from the identityProvider must be started using Activity.startActivityForResult(Intent, Int).

You can use any request code of your choice.

public class MyActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ZENKEY = 1234;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.my_layout);
       findViewById(R.id.myZenKeyButton).setOnClickListener(v -> startZenKeyAuthorization());
    }

    private void startZenKeyAuthorization(){
        startActivityForResult(
                ZenKey.identityProvider()
                    .authorizeIntent()
                    .withScopes(Scopes.EMAIL, Scopes.ADDRESS)
                    .build(),
                REQUEST_CODE_ZENKEY);
    }
}        

6.3 Using a WebView

ZenKey also allows your app to use a WebView to trigger the native SDK to allow users to sign in with ZenKey via AuthorizeIntentBuilder. This means that to call the Android SDK via a WebView, it is not necessary to override the ZenKey button. Rather, you would override the WebViewClient.shouldInterceptRequest method and use AuthorizeIntentBuilder to generate an Intent to call Context.startActivityForResult with the generated Intent.

For more on shouldInterceptRequest, please refer to the Android documentation on WebViewClient here. To learn about startActivityForResult, visit the Context class page here.

7.0 Handling the Authorization Response

The authorization result is returned by the ZenKey SDK using an Intent. Therefore, to receive the responses from your ZenKey authorization requests, your Activity must be designed to receive the Intent.

To handle such callbacks, it is best to use the onActivityResult() method. For even if you do not specify any PendingIntent for your request, the result will still come back in the requesting Activity/Fragment onActivityResult() method.

When the Activity ends, you will receive the result the authorization request passes back with onActivityResult(int requestCode, int resultCode, Intent data). Because the Intent will contain the result of the authorization request, you may then extract the AuthorizationResponse by calling the AuthorizationResponse.fromIntent(intent) method.

public class MyActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ZENKEY = 1234;

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       if (requestCode == REQUEST_CODE_ZENKEY) {
          if (resultCode == RESULT_OK) {
              handleAuthorizationResponse(data);
          } else {
             //Authorization canceled by user.
          }
       }
    }

    private void handleAuthorizationResponse(Intent data) {
       AuthorizationResponse response = AuthorizationResponse.fromIntent(data);
       if(response.isSuccessful()){
          // Authorization Succeeded
          finishZenKeyAuthentication(response);
       } else {
           //Authorization Failed
           handleZenKeyError(response);
       }
    }

    private void finishZenKeyAuthentication(AuthorizationResponse response) {
       String authorizationCode = response.getAuthorizationCode();
       String redirectUri = response.getRedirectUri();
       String mcc = response.getMcc();
       String mnc = response.getMnc();
       String codeVerifier = response.getCodeVerifier();
       // Send these values to your backend to finish the authentication process.
    }

    private void handleZenKeyError(AuthorizationResponse response) {
       AuthorizationError error = response.getError();
       switch (error){
          case AuthorizationError.INVALID_CONFIGURATION:
             break;
          case AuthorizationError.INVALID_REQUEST:
             break;
          case AuthorizationError.REQUEST_DENIED:
             break;
          case AuthorizationError.REQUEST_TIMEOUT:
             break;
          case AuthorizationError.SERVER_ERROR:
             break;
          case AuthorizationError.NETWORK_FAILURE:
             break;
          case AuthorizationError.DISCOVERY_STATE:
             break;
          case AuthorizationError.UNKNOWN:
             break;
       }
    }
}

7.1 Proof Key for Code Exchange

Proof Key for Code Exchange (PKCE) is a security extension to OAuth 2.0 for public clients on mobile devices. It helps prevent the interception of the authorization code on its return trip over an insecure transport protocol (e.g. deep linking between applications).

To support PKCE, the ZenKey SDK generates values for codeVerifier, codeChallengeMethod, and codeChallenge, using each as follows:

  1. Authorization. The ZenKey SDK passes a codeChallengeMethod and codeChallenge in the authorization request, then includes the codeVerifier in the AuthorizationResponse.

  2. Code Verifier to SP Server. Your app gets the codeVerifier from the AuthorizationResponse and securely transmits it to your backend in order to include the codeVerifier in your token request.

  3. Token Request. Your secure server requests an Access Token from the carrier with the additional codeVerifier parameter.

  4. Access Token Sent. The carrier applies the codeChallengeMethod to the received codeVerifier, then compares the resulting codeChallenge to the codeChallenge received during the authorization request. If the two values match, the carrier grants an Access Token. If not, the server denies the request and throws an error.

7.2 Implementing PKCE

In light of the flow above, to implement PKCE, your app must be able to:

  1. Receive the codeVerifier value in the AuthorizationResponse;

  2. Securely transmit the codeVerifier to your backend after a successful authorization request;

  3. Configure your server to receive the codeVerifier via the authorization endpoint;

  4. Include the codeVerifier value in the token request to ensure the request was authored from the same source and not intercepted.

7.3 PKCE Errors

  1. No Code Verifier. If the server requires PKCE, but the client does not send a codeVerifier, the authorization endpoint will send an error set to invalid_request. The error_description or response of error_uri will likely explain the error (e.g.codeVerifier required).

  2. Incorrect Code Verifier. Since ZenKey sends a hashed version of the codeVerifier in the authorization request, when the carrier receives this codeVerifier in the token request they can re-hash it and validate whether the two values match. If not, you will receive a request_denied error.

7.4 Using PendingIntent(s)

A PendingIntent is a description of an Intent and target action to perform with it. Objects returned by PendingIntent can be directed toward other applications in order to perform an action you specify. Developers integrating the ZenKey SDK may therefore specify a PendingIntent to handle either successful or unsuccessful authorization responses.

public class MyActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ZENKEY = 1234;

    private void startZenKeyAuthorization(){
        startActivityForResult(
                ZenKey.identityProvider()
                    .authorizeIntent()
                    .withSuccessIntent(PendingIntent.getActivity(this, Activity.RESULT_OK, new Intent(this, SuccessActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
                    .withFailureIntent(PendingIntent.getActivity(this, Activity.RESULT_OK, new Intent(this, FailureActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
                    .build(),
                REQUEST_CODE_ZENKEY);
    }
}        

Alternately, developers can specify a PendingIntent to start in case of completion. This PendingIntent is ignored if the above PendingIntents are specified as well.

public class MyActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ZENKEY = 1234;

    private void startZenKeyAuthorization(){
        startActivityForResult(
                ZenKey.identityProvider()
                    .authorizeIntent()
                    .withCompletionIntent(PendingIntent.getActivity(this, Activity.RESULT_OK, new Intent(this, CompletionActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
                    .build(),
                REQUEST_CODE_ZENKEY);
    }
}        

Developers can also specify a PendingIntent to start in case the authorization was cancelled.

public class MyActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ZENKEY = 1234;

    private void startZenKeyAuthorization(){
        startActivityForResult(
                ZenKey.identityProvider()
                    .authorizeIntent()
                    .withCancellationIntent(PendingIntent.getActivity(this, Activity.RESULT_CANCELED, new Intent(this, CancellationActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
                    .build(),
                REQUEST_CODE_ZENKEY);
    }
}        

Then, inside the started Activity, the developer can obtain the AuthorizationResponse using AuthorizationResponse.fromIntent(intent) passing the intent which started the activity as parameter.

public class MyResultActivity extends AppCompatActivity {

    @Override
        protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           AuthorizationResponse response = AuthorizationResponse.fromIntent(getIntent());
        }
}        

8.0 Using a Custom Redirect URI (optional)

By default, the ZenKey SDK uses and registers a default redirect URI in the form of <clientId>://com.xci.zenkey.sdk. For every request made using the ZenKey SDK, the corresponding AuthorizationResponse will contain the redirect URI used for the request.

To use a custom redirect URI, you must override the default value in your manifest, then specify the custom redirect URI for your request with ZenKeyButton or identityProvider. Additionally, you should always check to make sure that your custom redirect URI is valid.

8.1 Override the default Redirect URI in manifest

<activity android:name="com.xci.zenkey.sdk.RedirectUriReceiverActivity"
            tools:node="replace">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="com.mydomain" android:host="authorize"/>
    </intent-filter>        
</activity>

8.2 Set the Custom Redirect URI with ZenKeyButton

If you use the ZenKeyButton, you set the custom Redirect URI as follows:

public class MyActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_layout);
        ZenKeyButton zenKeyButton = findViewById(R.id.myZenKeyButton);
        zenKeyButton.setRedirectUri(
                new Uri.Builder()
                    .scheme("com.mydomain")
                    .authority("authorize")
                    .build());
    }
}

8.3 Set the Custom Redirect URI with IdentityProvider

If you use the identityProvider, you set the custom Redirect URI as follows:

public class MyActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ZENKEY = 1234;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.my_layout);
       findViewById(R.id.myZenKeyButton).setOnClickListener(v -> startZenKeyAuthorization());
    }

    private void startZenKeyAuthorization(){
        startActivityForResult(
                ZenKey.identityProvider()
                    .authorizeIntent()
                    .withRedirectUri(new Uri.Builder()
                                       .scheme("https")
                                       .authority("app.example.com")
                                       .path("/oauth2redirect")
                                       .build())
                    .build(),
                REQUEST_CODE_ZENKEY);
    }
}        

8.4 HTTP/HTTPS Redirect URI

HTTPS redirects are more secure and preferred over HTTP. If an HTTP/HTTPS redirect URI is required instead of a custom scheme, use the same approach and modify your AndroidManifest.xml:

<activity android:name="com.xci.zenkey.sdk.RedirectUriReceiverActivity"
        tools:node="replace">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="https"
              android:host="www.example.com"
              android:pathPrefix="/oauth2redirect"/>
    </intent-filter>
</activity>

8.5 Handling with IdentityProvider

If you use the identityProvider instead of the ZenKeyButton, you set the Redirect URI like this:

public class MyActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ZENKEY = 1234;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.my_layout);
       findViewById(R.id.myZenKeyButton).setOnClickListener(v -> startZenKeyAuthorization());
    }

    private void startZenKeyAuthorization(){
        startActivityForResult(
                ZenKey.identityProvider()
                      .authorizeIntent()
                      .withRedirectUri(
                              new Uri.Builder()
                                  .scheme("com.example.app")
                                  .authority("authority")
                                  .build())
                      .build(),
                REQUEST_CODE_ZENKEY);
    }
}        

9.0 Error Handling

The Error Type identifies the class of AuthorizationError. For more details, refer to the ERROR_KEY and ERROR_DESCRIPTION_KEY.

The following table summarizes the AuthorizationError error types and potential recovery suggestions for each.

Error Type Possible Cause Recovery Suggestion
INVALID_CONFIGURATION The wrong client_id was registered. Revise the client_id.
INVALID_REQUEST An invalid parameter was used for the current request. Check the request parameters.
REQUEST_DENIED This might occur when the user doesn't click deny/cancel Display an appropriate feedback message to the user
REQUEST_TIMEOUT The request took too long to process and is cancelled on the client side. This is not related to the server but to the device network connection (such as a bad connection). Use a feedback message such as "Unable to reach the server, please try again" or "Poor network connection..."
SERVER_ERROR The server was not able to handle the request. Check your server. This is not a network issue.
NETWORK_FAILURE There is no connection with the network. Display a feedback message advising the user to check their connection and try again.
DISCOVERY_STATE Triggered by the SDK during the discovery process. Examples: When the SDK is doing a loop between discovery and discover_ui because of a server bug, or when there is no browser available on the device to show the discover_ui/authorize webpages. Try to perform the authorization request again.
UNKNOWN An unknown error has occurred. If the problem persists, contact support.

9.1 Debugging

The SDK is offering a logging mechanism to help with integration and debugging. Logs are disabled by default, in order to activate the logs, developers can call the following method. The best place to enable the logs is in your application class if you have one, or in class before an actual request is started.

public class MyClass {

    public void myMethod(){
        ZenKey.logs(true);
    }
}

10.0 Token Request

With the user’s consent in the form of an authorization code, your secure server will request an access token from the token_endpoint discovered earlier. Information on setting up your secure server can be found in the ZenKey Server and Web Integration Guide.

To configure this request:

  1. Base64 encode your client_id and client_secret:
        Base64Encode(“{client_id}:{client_secret}”)

Note: When you retrieve your client_secret from the ZenKey Service Provider Portal, for security purposes we ask that you only store your client_secret on your secure backend and allow as few trusted personnel as possible access to it.

  1. Insert this encoded value in your Authorization header:

         'Authorization': "Basic {encoded_value_here}"
  2. Specify the Content-Type as URL-encoded:

         Content-Type: application/x-www-form-urlencoded
  3. Then include the following parameters in the body of your token request:

    grant_type="authorization_code"
    code="{auth_code}"
    redirect_uri="{auth_redirect_uri}"
    code_verifier="{codeVerifier}"

Here are all the components working together:

POST /token HTTP/1.1
Host: mno.com
Authorization: Basic {encoded_value_here}
Content-Type: application/x-www-form-urlencoded

    grant_type=authorization_code
    &redirect_uri=https://www.client.com
    &code={auth_code}
    &code_verifier={codeVerifier}

10.1 Token Response

In addition to an Access Token, the token response will return an ID Token which includes the sub — an identifier that uniquely pairs a single user with a particular client_id. When you receive the sub, you will store this unique ID in your user database for reference. Do not transmit this sub to your client app.

Sample Token Response:

HTTP/1.1 200 OK
Content-type: application/json
Cache-control: no-cache

{
    "access_token": "....",
    "token_type": "Bearer",
    "refresh_token": "8xLOxBtZp8",
    "expires_in": 3600,
    "correlation_id”:”xxxxx”,
    "id_token": "..."
}

10.2 Decode Your ID Token

The ID Token will need to be decoded. Once you decode the JWT payload, you will see it contains claims about the authentication of your end user. As a best practice, you should always validate the ID Token after you have decoded it. Specifically, you should verify the signature and claims contained within the ID Token. For more information about each parameter, refer to our Server and Web Integration Guide.

Sample Decoded ID Token:

{   
    "type"="at+jwt",  
    "kid":"xxx"
}

{
    "iss": https://mno.com,
    "sub": "mccmnc-client0001",  
    "aud": "ccid-sp00001",
    "nonce": "n-0S6_WzA2Mj",
    "exp": 1311281970,
    "iat": 1311280970,
    "auth_time": 1311280969,
    "acr": "a3",
    ...
}

11.0 Request User Info

After you exchange the authorization code for an authorization token on your secure server, you will be able to access the ZenKey userinfo_endpoint, which will pass information through your server's authenticated endpoints as defined by your application. Therefore, upon receiving an Access Token you may then request user info by issuing a GET request from your backend to the userinfo_endpoint discovered earlier.

    GET /userinfo HTTP/1.1
    Host: mno.com
    Authorization: bearer {ACCESS_TOKEN}

The response will contain JSON with the user information requested by your scopes and approved by the user.

{
    "sub": "mccmnc-123456789",  
    "name": "Jane Doe",  
    "given_name": "Jane",  
    "family_name": "Doe",  
    "email": "janedoe@example.com",  
    "email_verified":"true",  
    "postal_code": "90210-3456",  
    "phone_number": "+13101234567",  
    "phone_number_verified":"true"  
}

12.0 Account Migration

Note: Support for account migration is currently unavailable. The following section is for informational purposes only.

When users change carriers, ZenKey provides you with the support you need. This section describes how the migration process works and best practices for when a user ports his/her account from one carrier to another. But to see how account migration works, let us first imagine a user with these traits:

  • phone=1234567890
  • mccmnc=310010
  • sub= 310010-{carrierid}

Note: The mccmnc variable is a concatenation of Mobile Country Code (MCC) and Mobile Network Code (MNC). This six-digit number resides on every SIM card. As for sub, this variable is a pairwise identifier that ties a particular user to a particular client_id.

With this in mind, now imagine:

  1. The user above visits your app associated with client_id=ccid-SP0001.

  2. Your app with client_id=ccid-SP0001 detects the user as SUB=001001-B and stores/federates the user as verify=001001-B.

12.1 Migration Flow

  1. Now the user migrates phone number 1234567890 from Mobile Network Operator #1 (MNO1) to Mobile Network Operator #2 (MNO2), keeping their device but changing SIM and mccmnc.

  2. The user migrates quickly (e.g., within 15 minutes), so when they try to use your app with client_id=ccid-SP0001, you trigger a login prompt with the phone number.

  3. The SDK in your app submits a discovery request with the new mccmnc to retrieve the OpenID configuration for MNO2.

  4. The SDK in your app constructs the authentication URL, opening in the device browser to MNO2's web authentication endpoint.

  5. MNO2 sees the mobile user agent and posts a banner encouraging the user to download the ZenKey app.

  6. The user downloads, installs, and launches the ZenKey application for MNO2 .

  7. MNO2 notices that the user is not yet registered for the new phone number and: a. Asks: “Would you like to register a new ZenKey carrier account?” or “Would you like to PORT your prior ZenKey carrier account from your previous MNO?” b. Upon seeing that the device has a recently migrated phone line, offers a migration option.

  8. The user selects the option to migrate the existing ZenKey carrier account and is redirected in a WebView to MNO1’s authentication endpoint. (Note: port_data is a scope reserved for carriers only. It informs the carrier to render to the user a confirmation for porting the user identity account to a new carrier. Only carriers using ZenKey may access this scope.) Because the MNO1 authentication request contains the port_data scope, MNO1 knows the authentication is for migration.

  9. MNO1 completes the user authentication before returning the user to MNO2: a. MNO1 may require the user to perform multiple recovery methods because EAP-AKA (SIM) authentication will not work for the user. b. MNO1 signs a port token for each Service Provider using the slow rotating key present in the OpenID configuration reference.

  10. The user completes MNO2 account setup steps: a. MNO2 prepopulates the registration page with the previous name, email, address, etc. b. The user chooses a new PIN. c. MNO2 stores port tokens for each of the previous Service Providers used at MNO1.

  11. MNO2 asks if the user wants to port his/her previously-defined consents for each service provider application, and then returns the user to the Service Provider application with an authentication code.

  12. MNO2 returns an access_token and id_token from the token_endpoint. The access_token is the bearer token when making requests to the userinfo_endpoint. The id_token is a JSON Web Token (JWT) that contains claims about the authentication of an end user by an authorization server, such as sub=mncmcc-002002-Z.

  13. But your backend does not recognize this suband so uses the ISS (i.e. discovery endpoint) to access MNO1’s port_token signing key and verify the signature of the port_token. The port_token is always encoded. However once decoded, its contents will contain the new sub:

    {
        "iss": "https://zenkey.oldmno.com",
        "sub": "mncmcc-002002-Z",
        "iat": 1516239022,
        "aud": client_id
    }
  1. Your backend updates the user's sub in its database with the new value. For example, mncmcc-001001-B would become mncmcc-002002-Z.

12.2 Migration Best Practices

When you first authenticate a user, the first id_token should contain a single subject claim. Store this as a reference rather than a phone number or email address, since these profile attributes are liable to change. If you receive a new id_token that contains an AKA claim and an unrecognized sub, you should:

  1. Open the AKA port_token.

  2. Verify the port_token issuer is a trusted carrier. (The Service Provider Portal will contain a list of valid iss URLs.)

  3. Use the port_token :iss value to extract the OpenID configuration` of the old MNO.

  4. Use the OpenID configuration to extract the JWKs for the old MNO.

  5. Use the key ID (KID) in the port_token to identify which JWK key to use to verify the token's signature.

  6. If you have recorded a user with the old subject, update the references to the new subject from the new carrier.

Note: Because a user may choose not to port his/her ZenKey account, or to change carriers by getting a new phone number with the new carrier, you should host methods to update the ZenKey references.

Support

For technical questions, contact support.

License

Copyright © 2020 ZenKey, LLC.

Revision History

Date Published Document Version ZenKey SDK Version Description
5.01.20 0.0.02 1.0.0 Removed Apache Legal information.
1.31.20 0.0.01 1.0.0 First published version.

Last Update: Document Version 0.0.02, May 01, 2020