Auth Code Flow and Carrier Discovery

The ZenKey API exposes one main entry point, authorize, which is a function that takes a number of different parameters for further processing downstream by the ZenKey app (i.e., the carrier-specific app that supports ZenKey) or exiting with an error context.

This section describes ZenKey's primary and a secondary flows. Both terminate with an authorization code and a valid, ZenKey-supported 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. As the merchant, you are responsible for passing this information to your server to make a token request.

Primary Device Flow

Before a user can sign in to 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 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 users to set up their devices as a primary device if they have yet to do so.


This primary device is also the device with which users can authenticate requests from other devices, such as desktops and tablets.

  1. 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 — determines the users' wireless carrier. The carrier returns an authorization code via your Redirect URI (see Section 4.1 Configure Your Redirect URI).
  2. Your app or website makes an authorization code request to the local ZenKey app.
  3. If the user consents to share information, your backend server issues a token request for user info and other resources.

Secondary Device Flow

The secondary device flow denotes the "Trusted Browser Flow" for users' secondary devices such as a desktop browser or tablet. In this case, users perform authorizations for your app from a web browser/secondary device on which the app is not installed alongside the ZenKey app.

When users press the ZenKey button on a secondary device, they see a visual and numeric code. This code allows users to associate that secondary device with their primary devices.

  1. The SDK presents the carrier Discovery UI. If the user is authorizing a secondary device from an app on a tablet, the SDK uses a webview for this step.
  2. Users then scan the visual code or enter the numeric code into the ZenKey app on their primary device.
  3. Once users approve the request in the ZenKey app on their primary devices, 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.
  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.
  5. If users consent to share information, your backend server issues a token request for user info and other resources.


During discovery, your application determines the correct OpenID authentication request URL to construct. If users accessed the ZenKey application on their devices they need only perform discovery once per browser at worst, and at best only when accessing your service from their non-primary devices.

If users tried to sign in to your app or website from a new desktop browser, for instance, they need to authenticate by accessing their primary devices to verify their ZenKey identity. The discovery endpoint would then load the discovery-ui web interface where users either scan a visual code or enter a manual code on the browser into their primary devices. Once users verify their ZenKey account on a secondary device/browser, they will not be required to repeat the discovery process again for that particular device/browser.


To prevent fraud, users will never be asked to provide a phone number in the web interface of the discovery service page.

Sample Request

Sample Response<xxx>&mccmnc=<xx>&state=<state>


login_hint_token is an optional parameter returned only if the request is made by a secondary device, such as a desktop browser or tablet. Requests that originate on a user's primary device will not return this parameter. Only include login_hint_token if present in the discovery response. Do not include login_hint_token if the parameter is not present in the response.

For more information about discovery, please refer to integration options on the web.

Authorization Code Request, Universal Link Captured

Once discovery is completed, your app's SDK (Android, iOS, or JavaScript) constructs the request for the auth code. The ZenKey app on a device captures this standard OpenID Connect request which registers the universal link or is serviced by the carriers’ web interface as an alternative.


If users have a carrier-specific ZenKey application on their devices already, the common request will be serviced by the carrier natively or via the web. Otherwise, users are instructed to download and install the ZenKey application.

Sample Authorization Code Request 

Normal Authorization Code Request vs. Server-Initiated Flow

The majority of authorization code requests look a lot like the one shown directly above. However, if you configured your project for a server-initiated flow you would send a POST to the server_initiated_authorization_endpoint and receive a 200 response. This scenario differs from the regular authorization code request, which supports both GET and POST, and returns a 302 response. Another key difference for a server-initiated flow is that the response_type in your request must be set to async_token instead of code. Assuming these criteria have been met and the request contains a valid request JWT signed by you, the server-initiated response would resemble the following:

HTTP/1.1 200 OK
Content-type: application/json


SIM and User Authentication

User authentication can fail for any number of reasons. Perhaps a user gets a new SIM card, transfers their SIM card to a new device, or simply resets their device. In each scenario, a carrier detects a device_change, ZenKey might trigger a fraud alert, and a user fails to authenticate.

If, however, a user keeps their device yet changes their address, such a profile_change still allows that user to authenticate with your service. As a best practice, record your users' sub—the identifier that pairs a single user to a single application. Storing this value in a database allows you to make server-initiated calls to the carrier, instead of the phone number, and discern a device_change from a profile_change .

Recovery Process

Whenever ZenKey launches on a user device, it verifies the following attributes:

Carrier User ID
IMSI: Identity of the SIM card
IMEI: Identity of the device
Application key: an application instance key for the ZENKEY application.

In the wake of a device_change, ZenKey triggers a recovery flow that challenges the user to provide three factors (methods) for re-associating the new attributes to users' accounts. During this process, ZenKey presents separate screens where users select the authentication method and successfully provide the requested information.

Example recovery methods include:

  • Biometrics (fingerprints, face scans)
  • ZenKey PIN numbers
  • Email verification
  • Carrier login information
  • Single-use recovery codes
  • Trusted secondary devices

In addition to re-associating a ZenKey user after a device_change, the recovery process is also beneficial for users who get locked out of their accounts. In addition, recovery is necessary for users who wish to transfer their ZenKey account from one mobile provider to another. In this last scenario, users would:

  1. Select their former carrier.
  2. Sign in to their former carrier's account.
  3. Confirm their identity by providing at least three ZenKey recovery methods.
  4. Sign in to their new carrier's account.
  5. Create their new ZenKey account.

Once users complete these steps, ZenKey informs them that all their data was transferred to their new carrier's account.

Configuring Redirect URIs

When a user authorizes your application, the ZenKey SDK sends an authorization code request to the appropriate carrier then redirects users back to the app via your Redirect URI. This Redirect URI may also be used for callbacks to several ZenKey services.

Using the Recommended Redirect URI

The Redirect URI field in the ZenKey Portal is pre-populated with a recommended value that starts with your Client ID, but whose value you may change to a custom Redirect URI (see section directly below).

The ZenKey mobile SDKs assume the recommended Redirect URI is included. If you change this value, you must take extra steps to integrate a custom universal link for your mobile apps.

Using Multiple Redirect URIs
You may also choose to keep the recommended Redirect URI, and add additional ones.

Providing multiple Redirect URIs can be useful if you wish to use unique Redirect URIs for different environments (development vs. production, for example) or for different platforms that share the same Client ID. Additionally, you may want to receive callbacks at different URLs depending on the authorization type.

If you configure your Redirect URI in the ZenKey Portal, its scheme must resemble one of the following formats:

    - {client_id}://com.example.provider.sdk

    - https://URL/URI

    - {custom}://


As a general rule, you should also configure one or more valid Redirect URIs in the Redirect URIs attribute of your Client ID metadata, doing so as a space-separated list of redirects that are exact matches, with no wild-carding.

Token Request

As a merchant, you must make a token request using your registered client_id and a JWT client assertion signed by your registered keys. Because you cannot secure secrets on a mobile device, we recommend using a server instance for these requests.

To make requests directly from a mobile device, ZenKey recommends your client first secure a signed assertion from your server. The client can then use that signed assertion in the request. For more info, please refer to the key binding section of this document.

In the example below, your client has secured the auth code and passed it to your server to get the token. Here, your server must re-run discovery in order to identify the carrier's endpoint data.

Token Request


Sub Values



Required: Merchants must construct POST messages to the token endpoint.

Header: Authorization

Optional: Base 64 encoded client_id:client_secret as defined with OIDC. Service Providers that have registered a JWK or JWK_URI should leave this blank and use the client assertion in the body instead.


Required: All carriers must support application/JSON.

Optional: Method for Service Provider SDK (or sample code) to report a version.



Required: An auth code from the authorization endpoint, or the use or a refresh token from the token endpoint.


Required: Merchant must provide the same redirect as was used for the auth code. Using a different (whitelisted) Uri will result in an error.


Required: value must be the same as received from the authorization endpoint.


Optional: refresh tokens are discouraged for Zenkey, as you should use Zenkey for a point in time event, not session management.



Merchants may pass a correlation id to be added to MNO logs. Merchants must work with the CCID onboarding portal to request any log entries. Regex= ^[a-zA-Z0-9-]{8,16}$

Note: use the same correlation_id for code, token, and userinfo requests.

Optional: the Proof Key for Code Exchange (PKCE) code verifier may not be supported by all carriers. When supported, a client that provides a `code_challenge` and `code_challenge_method` must provide the matching validator to the token endpoint.

For more information on PKCE, please refer to PKCE errors on Android or PKCE errors on iOS.



Required: define this assertion type


Required: sign a JWT. Issued ID_Tokens will contain key-binding statements to the key used for this request.






Token Endpoint URL


Use for transaction_id or correlation_id; used to prevent replay.


Required: Expire: Number of seconds since 1970-01-01T0:0:0Z as measured in UTC Note: Best practice is for an SP to not issue client assertions for more than 24 hrs.


Required: Issued at: Number of seconds since 1970-01-01T0:0:0Z as measured in UTC NOTE: MNO’s may/must reject client assertions that are older than <24 hrs> regardless of the expiry time defined by the merchant server signing the assertion.


Used to request an ID Token for use by an on-device (or angular) client. Insert your client’s Public Key as the referred_binding. This allows you to request the ID Token and Access Token to be issued.

Sample Request

POST /token HTTP/1.1 
Host: Authorization Basic Y2xpZW50aWQ6Y2xpZW50c2VjcmV0 
Content-Type: application/x-www-form-urlencoded 

POST /token HTTP/1.1 
Host: Authorization Basic Y2xpZW50aWQ6Y2xpZW50c2VjcmV0 
Content-Type: application/x-www-form-urlencoded 


Token Response


Sub Values



Use the access token to access the carrier’s userinfo_endpoint.

The access_token is a JWT. Each carrier may enable an access_token or id_token to make other carrier API calls.

These may be gated by other carrier specific scopes.


iss, exp, iat, sub, amr, acr, scopes, cnf.

Attributes that will not need to be in the access token but will be in the id token include: aka, claims, context.



You will receive only bearer access tokens for now.


If the you have the offline_access scope, you may be able to get a refresh_token, whose value may be a JWT or the same as the access_token, depending on the carrier.


When submitting a server-initiated request, a client may ask for a time out to see how long the push message should be valid for before getting canceled. Do not submit a request for more than 48 hrs. Carriers may respond with errors or drop requests that exceed this time frame.


The same correlation_id that was passed to the carrier in the request.


You will receive an id_token if you defined the openid scope.


The iss should match the value discovered for this mobile network operator.


All date formats (request, client assertions, ID Tokens, and audit logs need a consistent date format and number of seconds since 1970-01-01T0:0:0Z as measured in UTC


Number of seconds since 1970-01-01T0:0:0Z as measured in UTC


Should always start with “mccmnc- "

aka: port_token:{jwt from old mno}

You will use the old carrier's key to validate the port_token. The port_token header should indicate the KID for you to use.

Note: kid id = Regex=^ccid-([a-zA-Z0-9]){1,128}$

Encoded port_token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6   Inh4eHh4In0.eyJpc3MiOiJodHRwczovL2NjaWQub2  xkbW5vLmNvbSIsInN1YiI6Im1uY21jYy03NTk4ND  czMTU4NzIzNGFzZGYiLCJpYXQiOjE1MTYyMzkw  MjJ9.SHu1RNCsVMs5XNIgUvkaJwAErbuw23sNjXgsFvwpr3w 

Decoded `port_token`:





Note: User interaction will be done for every request, so auth_time should always be current. If SSO is supported, then auth_time may have occurred significantly before iat (token issued time). Number of seconds since 1970-01-01T0:0:0Z as measured in UTC.


Sample values: a1, a3


hwk: will be present for ZenKey flows and indicates the SIM hardware key has been verified. The SIM is registered to a specific user.

fpt: will be present when the user provides a fingerprint within the timelines captured by the acr_values definitions. This may or may not be a FIDO library.

fid: is present when users provide a faceid within the timelines captured by the acr_values definitions. This may or may not be a FIDO library.

pin: is present when users confirm the PIN within the timelines captured by the acr_values definitions.

rf: a refresh token was used to issue this token. Any original AMR should be replaced with rf.

NOTE: carriers may also use other authenticators, therefore return different AMRs. This may occur during recovery, such as users making their first requests on a new device. Here users give multiple recovery methods, in which case AMR may include these additional recovery methods.

voice: voice print authentication
img: some form of photo authentication
ptrn: some form of remembered pattern unique users
cnt: contacts
pwd: password
otp: recovery code
mca: multiple channel authentication (e.g. trusted device)


Any truncated text that users were shown from the original request.


Optional: List of scopes that the user has approved for this merchant. Note: the id_token may contain all approved scopes, while the access token may include scopes approve with this specific request.

usn or others

Optional: Carriers may issue attributes like unique session numbers.


This parameter in an issued id_token and/or access_token indicates the public key to which the token has been issued. This enables clients to leverage key binding on requests that clients make using the token.

Sample 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, 
    "id_token": "..." 


ZenKey supports capturing user information based on the user's providing permission to you. The predefined scopes supported by ZenKey include:

  • openid
  • email
  • name
  • phone
  • postalCode

You must make a userinfo request using your access token to receive any of the user profile attributes. Merchants that only need authentication or 2nd-factor authentication need not make the userinfo request.
The userinfo_endpoint extracted from the discovered carrier’s iss/.well-known/openid-configuration indicates the destination for this call.

Userinfo request

Submit a userinfo request using your access token to receive any of the user profile attributes. The userinfo_endpoint extracted from the discovered carrier’s openid-configuration identifies the destination for this call.


Sub Values



A merchant must construct GET messages to the userinfo_endpoint.

Header: Authorization

Bearer <access_token>

The access_token received from the token_endpoint.


Key binding will require you (or via a Referred Binding, your client) to sign your API request.

All carriers ignore unrecognized parameters. This allows development of new features.


Optional: method for SDK or sample code to report a version.

Sample Request

GET /v1/userinfo?sdk_version=1.1 HTTP/1.1
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMzQ1In0.eyJpc3MiOiJodHRwczovL3hjaWp2LnQtbW9iaWxlLmNvbSIsImlhdCI6IjE1NTI4NzU4ODIiLCJleHAiOiIxNTUyODc1ODgyIiwiYXVkIjoiY2NpZC1zcGFwcGxpY2F0aW9uMDAxIiwic3ViIjoiMzYwMjEwLTMzMnNmMjM0MzAxLTEyNDRqNDNnYmprczMzIiwiYWNyIjoiYWFsMyIsImFtciI6WyJod2siLCJmcHQiXSwiY25mIjp7Imt0eSI6IlJTQSIsIm4iOiJ3Z3c2aGs0Z1dNcUFmVS1SMnFNT1lIRHdTQ0lwYlMxY2pPTDZQRThZbXRYTTRLNU5seGgtYmVIYkZxM0Z5RjQwR256Vy16eW5wVjJYVVZOLXJ5Q0FHbVFtZEFMT1M3WkF1UV9kX2lqdlR4U3hwQjhWb3RodU41MmtmWVRwNnlwWWpJem5maVVWSFFLWFdQSFA4R0NXdzhSaHJzTzF2WHR6TEN3QVJSQ3ZrNXowMVNiVHZDTWVfQllyaDBhS0ZuMU9hWWUzSlgwM3NycDNUZGdUYl9KanVtVWdZZ1FKT3JUZkJjS1pZN1BlbWY5eDV6RUxyVjlmd0JaREotWWZoTkJGZUFvQ3o3N1VXczM4NkZqWlNJbHhwb2xWRlI2ek5jSjJ2WTBfNDhtLTJxTVpMWVpBRjItalRjZ2x2Mlo4YXVPVHM4dHU1R1hNUDEtRmtORURKcVlIMXciLCJlIjoiQVFBQiJ9fQ.czReSZPxr5BbFt68_XSSrfbpyss6k5ZgKhM1gJj9_fU
x-authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIxNTI0ODY3NDE1IiwiZXhwIjoiMTUyNDg3MTAxNSIsImFsZyI6InNoYTI1NiIsImVodHMiOiJhdXRob3JpemF0aW9uIiwiZWR0cyI6IjZhZjNlYTlhOGIyMzdkNDQwZDIzNjdkNzQ2MmNjMmE1NjgzYmJmMjZhYjg0Y2EyMDZmNmMyY2ZkYTJhNTQ1NDIifQ.MzHzE4_IsOKe4w6JOKP21GrNqaaWomj3Y_SQzMaTGRs

Userinfo Response

{   “sub”:”mccmnc-1234567abcdefghijk”,
    “name”: {
        “value”:”Jane Doe”,
        “given_name”: “Jane”,
        “family_name”: “Doe”},
        “value: “[email protected]”},
    “postal_code”: {
    “phone”: {

User Consent

Because ZenKey does not provide a silent user experience (i.e., an implicit flow where the user experience is seamless across a single page) and the prompt parameter is not the same across the different carriers, users must approve a transaction with each request (referred to as explicit user consent) using the user's primary device.

What’s Next
Did this page help you?