Skip to content

Latest commit

 

History

History
647 lines (465 loc) · 16.2 KB

File metadata and controls

647 lines (465 loc) · 16.2 KB

TS43 SIM Verification Guide

Quick guide for implementing SIM-based phone number verification (no SMS needed).

Table of Contents


What is TS43?

TS43 enables SIM-based phone verification without SMS codes:

No SMS - Uses SIM card directly
Seamless - User just confirms on their device
Secure - Device-backed credentials
Fast - No waiting for SMS delivery

Perfect for: Login flows, phone verification, KYC processes


Quick Setup

1. Configure Auth Servers

In config/default.json:

{
  "auth_servers": [
    {
      "id": "stage",
      "url": "https://api.stage.ipification.com/auth"
    }
  ]
}

2. Configure Client

Add SIM flows to your clients:

{
  "clients": [
    {
      "user_flow": "pvn_sim",
      "title": "SIM Verification",
      "scope": "openid ip:phone_verify"
    }
  ]
}

3. That's it!

The app handles TS43 flow automatically. Users will see "SIM" buttons in the UI.


How to Use

User Flow (from user perspective):

  1. User clicks "SIM" button
  2. Browser requests credential from device
  3. User confirms on device
  4. App shows verified phone number

No SMS, no typing!

Developer Flow (from your code):

// Frontend automatically calls:
1. POST /ts43/auth  get digital_request
2. navigator.credentials.get(digital_request)  get vp_token
3. POST /ts43/token  get user info

// You don't need to implement these - they're already in login.js

Configuration

Required Config Fields

See CONFIG_DEFAULT_JSON_SAMPLE.md for full config guide.

Minimum required:

  • auth_servers: At least one server
  • client_id and client_secret: Your OAuth credentials
  • realm: Usually "ipification"

Frontend Integration

HTML Button (already included)

button#pvn_sim SIM Verification

JavaScript Handler (already included in login.js)

The app already handles TS43 flow. The relevant code is in:

  • public/js/login.js - See start_pvn_sim() and start_login_sim() functions

You don't need to write frontend code - it's already implemented!


Troubleshooting

Error: "Invalid server_id"

Cause: The server_id doesn't exist in your auth_servers config.

Solution: Check config/default.json and ensure server_id matches configured servers.

Error: "No auth servers configured"

Cause: auth_servers array is empty or missing.

Solution: Add at least one server. See Quick Setup.

SIM button not showing

Cause: Client not configured or device/browser compatibility.

Solution:

  • Check clients array includes pvn_sim or login_sim
  • Test on Android with Chrome/Edge (best compatibility)
  • Ensure mobile data is enabled

Authentication fails or times out

Solution:

  • Verify OAuth credentials are correct
  • Check auth server is accessible
  • Confirm carrier supports TS43
  • Try with different SIM card

Technical Details

Architecture Overview

Key Stages

IMPORTANT: The client app calls only /ts43/auth and /ts43/token. All calls to IPification Service are done by the backend.

  1. Client -> Backend: POST /ts43/auth
  2. Backend -> IPification: ciba/auth
  3. Backend -> IPification: ts43/dcql
  4. Backend -> Client: auth_req_id + digital_request
  5. Client -> Credential Manager: request credential
  6. Credential Manager -> Client: vp_token
  7. Client -> Backend: POST /ts43/token
  8. Backend -> IPification: ts43/callback
  9. Backend -> IPification: openid-connect/token
  10. Backend -> IPification: openid-connect/userinfo
  11. Backend -> Client: user info response

Prerequisites

Backend Requirements

  • client_id and client_secret (configured in config/default.json)
  • realm (default: ipification)
  • auth_servers configuration with at least one server

Integration Steps

End-to-end flow (frontend → backend → IPification)

  • Frontend calls POST /ts43/auth on your backend with client_id, server_id (optional), and optional login_hint, carrier_hint, scope, operation
  • Backend resolves auth server URL from server_id (defaults to first server if not provided)
  • Backend calls IPification CIBA auth: POST /ext/ciba/auth → gets auth_req_id
  • Backend calls IPification DCQL: POST /ext/bc/ts43/dcql with operation + nonce → gets dcqlResponse.data
  • Backend returns to frontend: auth_req_id, nonce, and digital_request (built from dcqlResponse.data)
  • Frontend calls Credential Manager with digital_request → receives vp_token
  • Frontend calls POST /ts43/token with vp_token, auth_req_id, client_id, nonce, server_id (optional)
  • Backend calls IPification callback: POST /ext/bc/ts43/callback
  • Backend exchanges token: POST /openid-connect/token → gets access_token
  • Backend fetches user info: GET /openid-connect/userinfo
  • Backend returns user info JSON to frontend

Step 1: Call /ts43/auth

Goal: Start the CIBA flow and receive a digital_request for the Credential Manager.

Client request body

{
  "client_id": "your_client_id",
  "server_id": "stage",
  "login_hint": "+1234567890",
  "carrier_hint": "51010",
  "scope": "openid ip:phone_verify",
  "operation": "VerifyPhoneNumber"
}

Backend response body

{
  "auth_req_id": "<auth_req_id>",
  "nonce": "<uuid>",
  "digital_request": {
    "protocol": "openid4vp-v1-unsigned",
    "data": {
      "response_type": "vp_token",
      "response_mode": "dc_api",
      "nonce": "<uuid>",
      "dcql_query": {
        "credentials": [<dcqlResponse.data>]
      }
    }
  }
}

How the response is used

  • auth_req_id: used later in /ts43/token to exchange for an access token
  • nonce: pass back to /ts43/token for session correlation
  • digital_request: sent to the Credential Manager to obtain vp_token

How digital_request is built

After the backend calls DCQL (/ext/bc/ts43/dcql), it embeds dcqlResponse.data into the DC API request:

{
  "protocol": "openid4vp-v1-unsigned",
  "data": {
    "response_type": "vp_token",
    "response_mode": "dc_api",
    "nonce": "<ts43_nonce>",
    "dcql_query": {
      "credentials": ["<dcqlResponse.data>"]
    }
  }
}

Step 2: Get VP Token via Credential Manager

Goal: Use digital_request to retrieve vp_token in the browser.

Client request (browser)

const { auth_req_id, digital_request, nonce } = body;

const credentialResponse = await navigator.credentials.get({
  digital: {
    requests: [digital_request],
  },
});

Extract vp_token from the response

const credentialData = credentialResponse.token || credentialResponse.data;
const { vp_token } = credentialData || {};

const dataToken = {
  vp_token: vp_token["ipification.com"][0],
  auth_req_id,
  client_id: data.client_id,
  nonce,
};

Step 3: Call /ts43/token

Goal: Send vp_token + auth_req_id to complete CIBA and fetch user info.

Client request body

{
  "vp_token": "<vp_token>",
  "auth_req_id": "<auth_req_id>",
  "client_id": "your_client_id",
  "nonce": "<nonce_from_step_1>"
}

Important: The nonce value must be the same nonce returned from /ts43/auth in Step 1. This is used for session correlation and security validation.

Backend response body

{
  "sub": "<subject>",
  "phone_number": "+1234567890",
  "phone_number_verified": true
}

How the response is used

  • The client uses the user info JSON for login/session creation
  • The backend also stores the response in the session for /user/info

API Endpoints

1) CIBA Authentication Endpoint

POST /ts43/auth

This is the first client call. Backend initiates CIBA and DCQL with IPification Service.

Request Headers

Content-Type: application/json

Request Body

{
  "client_id": "your_client_id",
  "server_id": "stage",
  "login_hint": "+1234567890",
  "carrier_hint": "carrierX",
  "scope": "openid ip:phone_verify",
  "operation": "VerifyPhoneNumber"
}

Request Body Fields:

Field Type Required Description
client_id string Yes Your OAuth2 client ID
server_id string No Auth server ID (e.g., "stage", "live"). Defaults to first server
login_hint string No Phone number in E.164 format (e.g., +1234567890)
carrier_hint number No Carrier MCC+MNC code (e.g., 51010 for Viettel Vietnam)
operation string No Operation type: "VerifyPhoneNumber" or "GetPhoneNumber"
scope string No OAuth scopes (default: uses client's scope from configuration)

Parameters

Parameter Type Required Description
client_id string Yes OAuth client id
login_hint string No E.164 phone for VerifyPhoneNumber
carrier_hint string No Carrier hint (MCCMNC or vendor string)
scope string No Example: openid ip:phone_verify
operation string No VerifyPhoneNumber or GetPhoneNumber

Response (Success - 200 OK)

{
  "auth_req_id": "<auth_req_id>",
  "nonce": "<uuid>",
  "digital_request": {
    "protocol": "openid4vp-v1-unsigned",
    "data": {
      "response_type": "vp_token",
      "response_mode": "dc_api",
      "nonce": "<uuid>",
      "dcql_query": {
        "credentials": [<dcqlResponse.data>]
      }
    }
  }
}

Response Fields

Field Type Description
auth_req_id string Auth request id from CIBA
nonce string UUID for the DCQL request
digital_request object OpenID4VP DC API request

Backend -> IPification Calls

A) CIBA Auth
  • URL: ${AUTH_SERVER_URL}/realms/ipification/protocol/openid-connect/ext/ciba/auth
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded

Form body:

  • client_id
  • client_secret
  • scope
  • login_hint (optional)
  • carrier_hint (optional)

Example:

curl -X POST "$AUTH_SERVER_URL/realms/ipification/protocol/openid-connect/ext/ciba/auth" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "client_secret=$CLIENT_SECRET" \
  --data-urlencode "scope=openid" \
  --data-urlencode "login_hint=+1234567890" \
  --data-urlencode "carrier_hint=carrierX"
B) TS43 DCQL
  • URL: ${AUTH_SERVER_URL}/realms/ipification/protocol/openid-connect/ext/bc/ts43/dcql
  • Method: POST
  • Content-Type: application/json
  • Authorization: Bearer ${auth_req_id}

JSON body:

{
  "operation": "GetPhoneNumber",
  "nonce": "<uuid>"
}

Example:

curl -X POST "$AUTH_SERVER_URL/realms/ipification/protocol/openid-connect/ext/bc/ts43/dcql" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AUTH_REQ_ID" \
  -d '{"operation":"GetPhoneNumber","nonce":"<uuid>"}'

2) Token Exchange Endpoint

POST /ts43/token

Client sends the VP token and auth request id. Backend validates and returns user info.

Request Headers

Content-Type: application/json

Request Body

{
  "vp_token": "<vp_token>",
  "auth_req_id": "<auth_req_id>",
  "client_id": "your_client_id",
  "nonce": "<nonce_from_step_1>",
  "server_id": "stage"
}

Request Body Fields:

Field Type Required Description
vp_token string Yes VP token received from Credential Manager
auth_req_id string Yes Auth request ID from /ts43/auth response
client_id string Yes Your OAuth2 client ID
nonce string Yes Nonce from /ts43/auth response
server_id string No Auth server ID. Defaults to first server if not provided

Parameters

Parameter Type Required Description
vp_token string Yes VP token from Credential Manager
auth_req_id string Yes Auth request id from /ts43/auth
client_id string Yes OAuth client id
nonce string Yes Nonce returned from /ts43/auth

Response (Success - 200 OK)

{
  "sub": "<subject>",
  "phone_number": "+1234567890",
  "phone_number_verified": true
}

Response Fields

Field Type Description
sub string Subject identifier
phone_number string Verified phone number
phone_number_verified boolean Verification result

Error Response

{
  "error": "<message>",
  "status": 500,
  "data": {}
}

data is included when the upstream service returns a body.

Backend -> IPification Calls

A) TS43 Callback
  • URL: ${AUTH_SERVER_URL}/realms/ipification/protocol/openid-connect/ext/bc/ts43/callback
  • Method: POST
  • Content-Type: application/json
  • Authorization: Bearer ${auth_req_id}

JSON body:

{
  "vp_token": "<vp_token>"
}

Expected response (typical):

{
  "status": "ok"
}

Some deployments may return an empty body or vendor-specific payload. A 2xx response is sufficient.

B) CIBA Token Exchange
  • URL: ${AUTH_SERVER_URL}/realms/ipification/protocol/openid-connect/token
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded

Form body:

  • client_id
  • client_secret
  • grant_type = urn:openid:params:grant-type:ciba
  • auth_req_id

Success response (typical):

{
  "access_token": "<access_token>",
  "expires_in": 300,
  "refresh_expires_in": 0,
  "token_type": "Bearer",
  "scope": "openid"
}

Error response (typical):

{
  "error": "authorization_pending",
  "error_description": "The authorization request is still pending."
}
C) User Info
  • URL: ${AUTH_SERVER_URL}/realms/ipification/protocol/openid-connect/userinfo
  • Method: GET
  • Authorization: Bearer ${access_token}

Success response (typical):

{
  "sub": "<subject>",
  "phone_number": "+1234567890",
  "phone_number_verified": true
}


Troubleshooting

Error: "Invalid server_id: 'xyz'"

Cause: The server_id provided doesn't exist in auth_servers configuration.

Solution: Check config/default.json and ensure the server_id matches one of the configured server IDs (e.g., stage, live).

Error: "server_id is required"

Cause: The server_id field is missing from the request body (only applies if backend has strict validation).

Solution: Include server_id in your request body or configure the backend to use the default server.

Error: "No auth servers configured"

Cause: The auth_servers array in configuration is empty or undefined.

Solution: Add at least one auth server to config/default.json. See Prerequisites for proper configuration.


Related Documentation