Skip to content

Latest commit

 

History

History
361 lines (298 loc) · 14.5 KB

File metadata and controls

361 lines (298 loc) · 14.5 KB

IPification for Android

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.

1 . Android Requirements & Permissions

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).

2. ClearText HTTP supports

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

3 . Retrieving the User’s Phone Number (Optional)

Need the MSISDN for coverage checking?
Use Android’s Phone Number Hint API as shown in our snippet:

https://github.com/bvantagelimited/ipification-mobile-sdk-code-snippet/blob/main/android-phone-number-hint.md

4. Main Flow of Mobile SDK :

  1. Check Coverage
  • Call the Coverage API with the client's phone number (GET) through the Cellular Network .
  • Receive a response with: is_available- true: supported | false: not supported
  1. Start Authentication
  • Prepare the authorization request with required parameters
  • Call Authorization API with authorization request ( GET ) through Cellular Network.
  • Receive a code response with:
    • result directly via redirect_uri (1) or
    • redirection url (301 or 302) (2)
  • (1) -> Parser the response then return the result to client
  • (2) -> Perform all url(s) redirection until receive the result with redirect_uri (through Cellular Network)

Note: All requests must be performed via the cellular-network interface.

  1. Call your backend API (S2S) with code to handle the token exchange.

Authorization Request (HTTP)

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)
Parameters:
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.

5. Android Code Snippet


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

License

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.