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 Service Providers (SPs) 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 (OIDC) is an authentication protocol 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

ZenKey is simple to use — one method for users to authenticate into all of your apps and websites.

1.2.1 Authorization on a Primary Device

Users establish their mobile device as their primary device by installing the carrier specific ZenKey app on that device. After completing a simple initial setup, users are ready to use ZenKey with third-party applications. Pressing the ZenKey button in a third party app or website from their primary device starts the authentication process.

Note: This primary device is also the device users can use to authenticate requests from other devices, such as desktops and tablets. See Section 1.2.2 Authorization on Secondary Devices.

Primary Flow

Step 1:   The User's Service Provider's mobile app or website makes an authorization code request to the local ZenKey app.

Step 2:   The User's ZenKey app determines the appropriate wireless carrier to perform SIM and user authentication with and returns an authorization code to your Redirect URI (see section on Redirect URI).

Step 3:   Because your user has consented to share with you, your backend server may make a token request for user info or other resources.

1.2.2 Authorization on Secondary Devices

Users can also use ZenKey to authenticate on devices other than their primary device, such as a tablet. 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 user is taken to a website where they can select the appropriate carrier. This is known as the carrier Discovery UI website, and is where the user chooses the carrier associated with their primary device. 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 website gets redirected to perform authorization with a login_hint_token.

Step 4:   Your backend server makes an authorization code request to the appropriate carrier, to perform SIM and user authentication, and receives the auth code back at your Redirect URI.

Step 5:   Because your user has consented to share with you, your backend server may make a token request for user info or other resources.

1.3 User Data

To create a secure experience, users are only shared via a web request from your secure backend to the user's carrier's secure backend and includes the user's attributes.

User information is only shared with Service Providers upon user consent. Users are able to choose whether to share their data and specifically what data will be shared 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, ZenKey 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. By default, Redirect URIs issued by ZenKey will resemble {client_id}://com.xci.provider.sdk.

  • Identify which scopes, or user attributes, to capture when a user authorizes your app. During enrollment, users provide ZenKey with basic personal information (e.g. name, email, address). When they 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. But if you do include them, ZenKey will always encrypt, but never store, the personal data of your users.

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 in 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 Register 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

When making authorization requests, you can use the ZenKey default button that handles the interaction with the identityProvider and starts the authorization intent. This is done by adding ZenKeyButton to your layout or you can get the authorization intent from identityProvider.

6.1 Using ZenKeyButton

The ZenKey SDK provides a default button which will take care of the interaction with the identityProvider and start the authorization intent.

6.1.1 Add ZenKeyButton Inside Layout

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 (for example, if your login UX is presented in 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).

If using identityProvider, to obtain authorization intent you 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);
    }
}        

7.0 Handle Authorization Response

To receive the responses from your ZenKey authorization requests, your Activity must be designed to receive the Intent. When handling such callbacks, it is best to use the onActivityResult() method in the manner described below.

7.1 Using OnActivityResult()

If developers do not specify any PendingIntent for their request, the result will come back in the requesting Activity/Fragment onActivityResult() method.

Developers can then call AuthorizationResponse.fromIntent(data) passing the data result intent to obtain the AuthorizationResponse.

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 Succeed
          finishZenKeyAuthentication(response);
       } else {
           //Authorization Failed
           handleZenKeyError(response);
       }
    }

    private void finishZenKeyAuthentication(AuthorizationResponse response) {
       String authorizationCode = response.getAuthorizationCode();
       String mcc = response.getMcc();
       String mnc = response.getMnc();
       String codeVerifier = response.getCodeVerifier();
       // Send those four values to your back-end 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.2 Proof Key for Code Exchange

As shown above, all service providers should send a codeVerifier string to their backend. This codeVerifier serves as part of Proof Key for Code Exchange (PKCE), a security extension to OAuth 2.0 which 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.3 Implementing PKCE

In light of the flow above, all Service Providers must be able to:

  1. Receive the codeVerifier value in the AuthorizationResponse;

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

  3. Configure their 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.4 PKCE Errors

  1. No Code Verifier. If the server requires PKCE, but the client does not send a code_verifier, 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.code_verifier required).

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

7.5 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

Before issuing a token request, your application and the ZenKey SDK will re-perform discovery and use the discovered token endpoint to request an access token from ZenKey with the processes already detailed:

  • Auth Code
  • MCC (Mobile Country Code)
  • MNC (Mobile Network Code)
  • Redirect URI

As a result of the discovery call made from the ZenKey SDK, your backend server will receive the MCC/MNC and use it to issue a token request. The token received should serve as the basis for accessing or creating a token within the domain of your application. 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.

Information on setting up your secure server can be found in the ZenKey Server and Web Integration Guide.

11.0 Account Migration

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= 001001-{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 SP's client_id.

With this in mind, now imagine:

  1. The user above visits a Service Provider (SP) with client_id=ccid-SP0001.

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

11.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 the SP client_id=ccid-SP0001, that SP triggers a login prompt with the phone number.

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

  4. The SP SDK 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 CCID 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 SP 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 of the SPs, and then returns the user to the Service Provider application with an authentication code.

  12. The Service Provider issues an access_token and id_token. 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 the SP 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. The Service Provider updates the user's sub in its database with the new value. For example, mncmcc-001001-B would become mncmcc-002002-Z.

11.2 Migration Best Practices

When an Service Provider (SP) first authenticates a user, the first id_token should contain a single subject claim. The SP should store this as a reference rather than a phone number or email address, since these profile attributes are liable to change. If an SP receives a new id_token that contains an AKA claim and an unrecognized sub, the SP 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 the Service Provider has a recorded a user with the old subject, update the references to the new subject from the new carrier.

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

Support

For technical questions, contact support.

License

Copyright © 2019 ZenKey, LLC.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Revision History

Date Version Description
1.8.20 0.9.19 Removed Mobile Authentication Taskforce in Section 1.0 and updated OpenID Connect content in Section 1.1
12.18.2019 0.9.18 Added phrase about WebView log-ins (6.2)
12.10.2019 0.9.17 Edited Section 2.0. Corrected Token Request details in Section 10.0. Modified ZenKeyButton parameter ZenKeyButtonMode (6.1.3). Added Section 6.1.4 Button Text; incremented subsequent section heading numbers.
12.04.2019 0.9.16 Deleted License Notice. Changed XCI JV, LLC to ZenKey, LLC.
12.04.2019 0.9.15 Completely revised Section 3; added info on PendingIntent (Section 7.5) and other edits; switched Sections 10 and 11.
11.27.2019 0.9.14 Updated multiple sections.
10.4.2019 0.9.13 Added Account Migration section.
9.23.2019 0.9.12 Updated license with Apache 2.0 text.
9.9.2019 0.9.11 Revised legal footer and updated sections 1 and 2.
8.30.2019 0.9.10 Various edits and updated background section; Updated dependencies.
8.20.2019 0.9.9 Added section numbers; Added revision history; Added additional info about Redirect URIs to section 6.0.1.

Last Update: Document Version 0.9.19, January 8, 2020