This document describes the IPification Android SDK and its usage. The main purpose of the SDK is to provide network-based authentication for mobile users.
| Item | Details |
|---|---|
| Minimum OS | Android 5.0 (API 21) or higher |
| Device prerequisite | Mobile / cellular data must be enabled. All IPification requests (and redirects) are forced over the cellular interface. |
| Required manifest permissions | INTERNET, ACCESS_NETWORK_STATE, CHANGE_NETWORK_STATE, ACCESS_WIFI_STATE |
| Networking library | IPification ships with OkHttp 5 (socket binding + custom DNS). |
This configuration is only required for specific countries and telcos:
-
Indonesia: XL, Tri, Smartfren
-
Canada: TELUS
-
Mexico: Telcel
-
UK: 02
-
Sri lanka: Dialog
-
India: Jio
To support authentication with these telcos, which require cleartext network traffic, we need to enable cleartext traffic for their domains. https://github.com/bvantagelimited/ipification-mobile-sdk-code-snippet/blob/main/xml/ipification_network_security_config.xml
Need the MSISDN for coverage checking?
Use Android’s Phone Number Hint API as shown in our snippet:
- Check Coverage
- Call the Coverage API with the client's
phonenumber (GET) through the Cellular Network . - Receive a response with:
is_available-true: supported |false: not supported
- Start Authentication
- Prepare the
authorization requestwith required parameters - Call Authorization API with
authorization request( GET ) throughCellular Network. - Receive a
coderesponse with:- result directly via
redirect_uri(1) or - redirection url (
301or302) (2)
- result directly via
- (1) -> Parser the response then return the result to client
- (2) -> Perform all url(s) redirection until receive the result with
redirect_uri(throughCellular Network)
Note: All requests must be performed via the cellular-network interface.
- Call your backend API (S2S) with
codeto handle the token exchange.
GET https://api.ipification.com/auth/realms/ipification/protocol/openid-connect/auth?
response_type=code&
client_id={client-id}&
redirect_uri={client-callback-uri}&
scope=openid ip:phone_verify&
state={state}&
login_hint={login_hint}
-------
Response:
(1) 200 - redirect_uri?code=abcxyz&state={state}
(2) 302 - url redirection (ex: https://mnv.telco.com/webhook/api/webhook/auth?state=xyzabc)
| Name | Description |
|---|---|
| client_id | unique identifier of the client that is generated by IPification and provided to client during the onboarding process. |
| redirect_uri | is used when redirecting a user back to the client application. During the onboarding process, the client's redirect_uri will be provided, this value can represent wildcard uri and will be used to validate provided redirect_uri in the request. Redirect URI must be same accross all requestst in a flow. |
| scope | use openid ip:phone_verify for phone number verifying |
| login_hint | end-user phone number (MSISDN).Phone number should be specified according to the E.164 number formatting (http://en.wikipedia.org/wiki/E.164) without leading + sign. |
| mcc (optional) | Mobile Country Code |
| mnc (optional) | Mobile Network Code |
| consent_id (optional) | Unique ID for the consent that is traceable if consent audit is required. Value will be provided if needed in integration process. |
| consent_timestamp (optional) | The time stamp when consent was accepted by end user. Accepted format is UNIX time stamp in seconds. |
import android.annotation.TargetApi
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.util.Log
import com.ipification.mobile.sdk.android.interceptor.HandleRedirectInterceptor
import com.ipification.mobile.sdk.android.request.AuthRequest
import com.ipification.mobile.sdk.android.utils.LogUtils
import com.ipification.mobile.sdk.android.utils.NetworkUtils
import com.ipification.mobile.sdk.android.utils.debug
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.util.*
// Manifest.xml: required permission: INTERNET, ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE, CHANGE_NETWORK_STATE, android:usesCleartextTraffic="true"
// external library: OkHttp : com.squareup.okhttp3:okhttp 5.x
class CellularConnection {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun performRequest(context: Context, authRequest: AuthRequest) {
// wifi is OFF, DATA is ON -> request with current network interface
if (NetworkUtils.isMobileDataEnabled(context) && !NetworkUtils.isWifiEnabled(context)) {
processRequest(context, null, authRequest)
} else {
requestCellularNetwork(context, authRequest)
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun requestCellularNetwork(context: Context, authRequest: AuthRequest) {
// 1. force network connection via cellular interface
// If your app supports Android 21+, you need to implement handling timeout manually.
// Android 21++ support requestNetwork (NetworkRequest request,
// ConnectivityManager.NetworkCallback networkCallback)
// Android 26++ support requestNetwork(NetworkRequest request,
// ConnectivityManager.NetworkCallback networkCallback,
// int timeoutMs)
// https://developer.android.com/reference/android/net/ConnectivityManager#requestNetwork(android.net.NetworkRequest,%20android.net.ConnectivityManager.NetworkCallback)
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
connectivityManager.requestNetwork(
request,
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
processRequest(context, network, authRequest)
}
override fun onUnavailable() {
// cellular network is not available, call the callback error
Log.e("TestAPI", "cellular network is not available")
}
},
5000 // CONNECT_NETWORK_TIMEOUT
)
} else {
// manual adding timeout
connectivityManager.requestNetwork(
request,
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
isReceiveResponse = true
processRequest(context, network, authRequest)
}
override fun onUnavailable() {
isReceiveResponse = true
// cellular network is not available, callback
Log.e("CellularConnection", "cellular network is not available")
}
}
)
Timer().schedule(object : TimerTask() {
override fun run() {
LogUtils.debug("timeout isReceiveResponse=${isReceiveResponse} ")
if (!isReceiveResponse) {
handleUnAvailableCase(cellularCallback)
}
}
}, 5000)
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun processRequest(context: Context, network: Network?, authRequest: AuthRequest) {
// using OkHTTP library to make the connection
val httpBuilder =OkHttpClient.Builder()
// add dns if needed
if (network != null) {
// enable socket for network
httpBuilder.socketFactory(network.socketFactory)
// enable DNS resolver with cellularnetwork
val dns = NetworkDns.instance
dns.setNetwork(network)
httpBuilder.dns(dns)
}
//check and handle the response with redirect_uri
httpBuilder.addNetworkInterceptor(
HandleRedirectInterceptor(
context,
authRequest.getUrl(),
authRequest.mRedirectUri.toString()
)
)
val httpClient = httpBuilder.build()
val okHttpRequestBuilder = Request.Builder()
//url
okHttpRequestBuilder.url(authRequest.getUrl())
val okHttpRequest: Request = okHttpRequestBuilder
.build()
httpClient.newCall(okHttpRequest).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// handle the response
Log.i("CellularConnection", "callAPIonCellularNetwork RESULT:${response.body?.string()}")
}
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
})
}
}
--------------------------
NetworkDns.kt
Use this class to implement DNS. Should use the cellular network to resolve the IP of the hostname (in wifi case). Prior ipv4 over ipv6
--------------------------
import android.net.Network
import android.os.Build
import android.os.Build.VERSION_CODES
import okhttp3.Dns
import java.net.InetAddress
import java.net.UnknownHostException
class NetworkDns private constructor() : Dns {
private var mNetwork: Network? = null
fun setNetwork(network: Network?) {
mNetwork = network
}
@Throws(UnknownHostException::class)
override fun lookup(hostname: String): List<InetAddress> {
return if (mNetwork != null && Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
try {
val inetAddressList: MutableList<InetAddress> = ArrayList()
val inetAddresses = mNetwork!!.getAllByName(hostname)
for (inetAddress in inetAddresses) {
if (inetAddress is Inet4Address) {
inetAddressList.add(0, inetAddress)
} else {
inetAddressList.add(inetAddress)
}
}
inetAddressList
} catch (ex: NullPointerException) {
try {
Dns.SYSTEM.lookup(hostname)
} catch (e: UnknownHostException) {
Arrays.asList(*InetAddress.getAllByName(hostname))
}
} catch (ex: UnknownHostException) {
try {
Dns.SYSTEM.lookup(hostname)
} catch (e: UnknownHostException) {
Arrays.asList(*InetAddress.getAllByName(hostname))
}
}
} else Dns.SYSTEM.lookup(hostname)
}
companion object {
private var sInstance: NetworkDns? = null
val instance: NetworkDns
get() {
if (sInstance == null) {
sInstance = NetworkDns()
}
return sInstance!!
}
}
}
--------------------------
HandleRedirectInterceptor.kt (need to implement in case handling redirect urls)
By default,OKHttp3 automatically follow Redirect (followSslRedirects(true),followRedirects(true)). We just need to check and return the response if the location url starts with the `redirect_uri`
--------------------------
import android.content.Context
import android.os.Build
import android.util.Log
import okhttp3.*
import okhttp3.ResponseBody.Companion.toResponseBody
class HandleRedirectInterceptor(ctx: Context, requestUrl: String, redirect_uri: String) : Interceptor {
private var redirectUri: String = redirect_uri
private var url: String = requestUrl
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val response: Response = chain.proceed(request)
// check and return success response if location match with defined redirect-uri
if (response.code in 300.. 399){
if ((response.headers["location"] != null && response.headers["location"]!!.startsWith(redirectUri))
|| (response.headers["Location"] != null && response.headers["Location"]!!.startsWith(redirectUri)))
{
val builder: Response.Builder = Response.Builder().request(request).protocol(Protocol.HTTP_1_1)
val contentType: MediaType? = response.body!!.contentType()
val locationRes = response.headers["location"] ?: response.headers["Location"] ?: ""
val body = locationRes.toResponseBody(contentType)
builder.code(200).message("success").body(body)
// close the response body to avoid exception
response.body?.close()
return builder.build()
}
}
return response
}
}
More Detail Implementation: https://github.com/bvantagelimited/ipification-mobile-sdk-code-snippet/blob/main/android_IPificationService.kt
Copyright 2022 IPification, Inc.
Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for
additional information regarding copyright ownership. The ASF licenses this
file to you 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.