Authentication¶
The Authentication module allows you to configure your mobile application to access SAP Mobile Services using the following authentication methods:
- Basic
- SAML
- OAuth2
- Certificate
- API Key Only
If required, you can add One Time Password (OTP) to your authentication steps to provide two factor authentication for your app.
This module provides implementations of okhttp3.Interceptor
to handle different types of authentication. Many of these
interceptors must display UI to complete authentication. They therefore require access to the top activity of the
application.
To make the activity available, the application must call android.app.Application.registerActivityLifecycleCallbacks
with AppLifecycleCallbackHandler
.
The best place to register the callback is from onCreate
in an overridden android.app.Application
. For example:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());
}
}
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())
}
}
Make sure to add the custom Application
to the AndroidManifest.xml
as follows:
<manifest . . . >
<application android:name=".SampleApplication" . . . >
. . .
</application>
</manifest>
Recommended TLS Versions¶
By default, an OkHttpClient
will attempt to use the most recent TLS version starting with 1.3. However, it will fall
back to older versions (back to 1.0) to be compatible with older servers.
Keep in mind that TLS 1.0 has known vulnerabilities and in general the newer versions are more secure. SAP recommends that you only use newer TLS versions.
For example, to only allow TLS 1.2 and 1.3, you could use the following code:
List<ConnectionSpec> connectionSpecs = new LinkedList<>();
ConnectionSpec strict = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
.build();
connectionSpecs.add(strict);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectionSpecs(connectionSpecs)
<... add other things to the OkHttpClient.Builder as usual ...>
.build();
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
.build()
connectionSpecs.add(strict)
val okHttpClient = OkHttpClient.Builder()
.connectionSpecs(connectionSpecs)
<... add other things to the OkHttpClient.Builder as usual ...>
.build()
Basic Authentication¶
See the Javadoc for BasicAuthDialogAuthenticator
or for custom handling see Handling
authentication.
One Time Passcode (OTP)¶
General Usage¶
The OtpInterceptor
is capable of detecting OTP challenges in network responses. When a challenge is detected,
authentication is done with an OtpProcessor
. The default processor is OtpWebViewProcessor
.
The OtpConfiguration
class holds the configuration required by the interceptor and the processor. There are default
values for the challenge header name (x-smp-authentication
) and the challenge header value (otp-challenge
).
When constructing OtpConfiguration.Builder
, if the SettingsParameters
object is set for the SettingsProvider
class, the
authorization endpoint URL and the finish endpoint URL will have default values based on the SettingsParameter
object.
Otherwise, the authorization endpoint URL and the finish endpoint URL must be set explicitly.
// Create the OtpConfiguration using the SettingsParameters object hold by SettingsProvider class
SettingsProvider.set(SettingsParameter)
OtpConfiguration otpConfig = new OtpConfiguration.Builder().build();
// Add an OtpInterceptor to the OkHttpClient to handle the OTP challenges.
OkHttpClient samlWithOtpHttpClient = new OkHttpClient.Builder()
.addInterceptor(new SamlInterceptor())
.addInterceptor(new OtpInterceptor(otpConfig))
.cookieJar(new WebkitCookieJar())
.build();
// Create the OtpConfiguration using the SettingsParameters object hold by SettingsProvider class
SettingsProvider.set(SettingsParameter)
val otpConfig = OtpConfiguration.Builder().build()
// Add an OtpInterceptor to the OkHttpClient to handle the OTP challenges.
val samlWithOtpHttpClient = OkHttpClient.Builder()
.addInterceptor(SamlInterceptor())
.addInterceptor(OtpInterceptor(otpConfig))
.cookieJar(WebkitCookieJar())
.build()
Warning
The WebkitCookieJar
is required to make the cookies from the OTP authentication flow available to
subsequent network requests.
SAP Authenticator with OTP¶
SAP Authenticator is a mobile app that generates passcodes for systems that require OTP authentication.
Capturing the Passcode From SAP Authenticator¶
For SAP Authenticator to send your application the passcode, you must handle a specific scheme. If, for example, your
app ID is com.example.myapp
, then the scheme would be com.example.myapp.xcallbackurl
.
Add this to your AndroidManifest.xml
as follows:
<activity
android:name="com.sap.cloud.mobile.foundation.authentication.OtpResponseActivity"
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.example.myapp.xcallbackurl" />
</intent-filter>
</activity>
Setting up SAP Authenticator¶
For information about setting up an account on SAP Authenticator, see One-Time Password Authentication User Guide.
When you add your application to SAP Authenticator, use the following format for the URL:
com.example.myapp.xcallbackurl://x-callback-url/setCredential?username_paramvalue=[username]&passcode_paramvalue=[passcode]
replacing com.example.myapp
with your app ID.
SAML 2.0¶
General Usage of SAML¶
The SamlInterceptor
class is capable of detecting SAML challenges in the network request's response and starting the
SAML authentication flow.
Example:
// Create the SAML configuration for the server
SamlConfiguration samlConfiguration = new SamlConfiguration.Builder()
.authUrl("https://hcpms-xxxxxxxtrial.hanatrial.ondemand.com/SAMLAuthLauncher")
.build();
// Create an OKHttp intercepter to listen for challenges
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new SamlInterceptor(samlConfiguration))
.cookieJar(new WebkitCookieJar())
.build();
Warning
The WebkitCookieJar
is required to make the cookies from the SAML web flow available to subsequent network
requests.
Preauthentication with SAML¶
Generally, authentication occurs when a response is encountered with a SAML challenge. However, such a response could come at any point in the application’s lifecycle. You may find it undesirable to have a web view be displayed on top of the app at that point.
The SDK supports a way to implement preauthentication at application startup. This requires a direct call to the authenticate method of SAMLProcessor
.
Example:
SamlProcessor samlProcessor = new SamlWebViewProcessor(samlConfiguration);
try {
samlProcessor.authenticate();
} catch (IOException ex) {
// If there was an error during the authentication process
// or it was cancelled.
}
val samlProcessor = SamlWebViewProcessor(samlConfiguration)
try {
samlProcessor.authenticate()
} catch (ex: IOException) {
// If there was an error during the authentication process
// or it was cancelled.
}
Certificate Handling with SAML¶
By default the web view will ignore certificate challenges. To handle certificate challenges with the web view, construct
the SamlWebViewProcessor
with a CertificateProvider
, such as SystemCertificateProvider
.
OAuth 2.0¶
General Usage of OAuth¶
The OAuth2Interceptor
class is capable of detecting OAuth2 challenges in the network responses and starting the OAuth2
authentication flow.
The OAuth2Configuration
class holds the configuration. Most of the required configuration items can be found from the
configuration on the server. The redirectURI
need not be specified on the builder. If not specified, it will default to
a URI of the form <package name>://<host of authURL>
.
The corresponding custom scheme (the package name
) is automatically registered in the AndroidManifest.xml
file
included in the authentication aar
file. For this default redirectURI
to work, ensure the redirectURI
on the server
matches (you will likely have to modify the scheme on the server from HTTPS to the package name of your app). Any
scheme other than HTTPS is considered to be a custom scheme (note that the client's default redirectURI
has a custom
scheme). A custom scheme on the redirectURI
is required for OAuth with an external browser or Custom Tabs. If you
cannot use a custom scheme, then you must set the redirectURI
on the OAuth2Configuration.Builder
and use the
OAuth2WebViewProcessor
. If you need to use a different custom scheme than the default you must add the custom scheme
to the AndroidManifest.xml
.
Example:
// Server side OAuth2 configuration
OAuth2Configuration oauthConfiguration = new OAuth2Configuration.Builder(activity.getApplicationContext())
.clientId("<#Client ID hash#>")
.responseType("code")
.authUrl("<#URL String#>")
.tokenUrl("<#URL String#>")
.build();
// Storage for OAuth2 tokens
OAuth2TokenStore tokenStore = new OAuth2TokenInMemoryStore();
// Create an OKHttp intercepter to listen for challenges
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new OAuth2Interceptor(new OAuth2BrowserProcessor(oauthConfiguration), tokenStore))
.cookieJar(new WebkitCookieJar())
.build();
// Set global OKHttpClient. The OAuth flow will use this for requesting the token.
ClientProvider.set(okHttpClient);
// Server side OAuth2 configuration
val oauthConfiguration = OAuth2Configuration.Builder(activity.applicationContext)
.clientId("<#Client ID hash#>")
.responseType("code")
.authUrl("<#URL String#>")
.tokenUrl("<#URL String#>")
.build()
// Storage for OAuth2 tokens
val tokenStore = OAuth2TokenInMemoryStore()
// Create an OKHttp intercepter to listen for challenges
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(OAuth2Interceptor(OAuth2BrowserProcessor(oauthConfiguration), tokenStore))
.cookieJar(WebkitCookieJar())
.build()
// Set global OKHttpClient. The OAuth flow will use this for requesting the token.
ClientProvider.set(okHttpClient)
Note
The WebkitCookieJar
is not required. In this example it is used for persisting the cookies. The
JavaNetCookieJar
or other CookieJar
implementations can be used.
Warning
Be sure to call ClientProvider.set(okHttpClient);
when using OAuth2 or provide a OkHttpClient
to the
OAuth2 processor constructor.
To use a non-default custom scheme on the redirect URI, add a tag like this to the AndroidManifest.xml
inside the
application tag:
<activity
android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity"
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="customScheme"/>
</intent-filter>
</activity>
If an HTTPS redirect URI is required instead of a custom scheme, use the same approach:
<activity
android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity"
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="example.host.com"
android:path="/oauth2_redirect"/>
</intent-filter>
</activity>
PKCE with OAuth¶
OAuth 2.0 clients utilizing the authorization code grant are susceptible to the authorization code interception attack. The technique used to mitigate this threat is Proof Key for Code Exchange (PKCE).
Using the PKCE feature, the SDK generates a random code verifier
, and applies a one-way hash function (SHA-256) on the code verifier
to get a code challenge
. The code challenge
is sent with an OAuth authorization code request, and the code verifier
is sent with an OAuth token request.
The mobile services server applies the SHA-256 hash function on the code verifier
, and checks whether it yields the same code challenge
before returning any tokens.
Because only the client and server know the code verifier
, an attacker cannot, therefore, have the requisite information to make a valid token request.
PKCE with OAuth is transparent to client code. For details, please refer to the RFC.
Preauthentication with OAuth¶
Generally, authentication occurs when a response is encountered with a OAuth2 challenge. However, such a response could come at any point in the application’s lifecycle. You may find it undesirable to have a web view or browser be displayed on top of the app at that point.
The SDK supports a way to implement preauthentication at application startup. This requires a direct call to the authenticate method of OAuth2Processor
.
Example:
OAuth2Processor oAuth2Processor = new OAuth2BrowserProcessor(oauth2Configuration);
try {
OAuth2Token token = oAuth2Processor.authenticate();
} catch (IOException ex) {
// If there was an error during the authentication process
// or it was cancelled.
}
val oAuth2Processor = OAuth2BrowserProcessor(oauth2Configuration)
try {
val token = oAuth2Processor.authenticate()
} catch (ex: IOException) {
// If there was an error during the authentication process
// or it was cancelled.
}
Certificate Handling with OAuth¶
If OAuth2BrowserProcessor
is used, some browsers, including Chrome, ask the user to select a certificate from the
system keychain when a client certificate challenge is encountered. When using a web view processor, it will by default
ignore certificate challenges. To handle certificate challenges with the web view you must construct the
OAuth2WebViewProcessor
with a CertificateProvider
such as SystemCertificateProvider
.
Cross-Context SSO with OAuth¶
The cross-context SSO feature makes it easy for users to transfer OAuth tokens from desktops or device browsers to native apps. As a prerequisite, the cross-context SSO feature toggle under OAuth configuration should be turned on in SAP mobile service cockpit. Then the user can get the SSO onboarding URL and put it in their web application.
Users can leverage this feature in two ways, either by scanning a QR code or by using an app link:
-
Scanning a QR code is useful when a user opens the SSO onboarding URL on their desktop. The URL will display a QR code containing a short-duration passcode, allowing the native app to perform authentication. The user can open the native app, navigate to the QR code scanning screen, and scan the QR code before it expires. If successful, the native app will get onboarded automatically.
-
App links are appropriate when the user opens the SSO onboarding URL on a device. Note that this also requires that the app link feature be turned on in SAP mobile service cockpit. The URL will display a Next button to launch the native app and get it onboarded automatically.
The app link requires that customer apps configure it in the manifest file (this is not required for the QR code):
<activity
android:name=".app.SomeActivity"
android:theme="@style/AppTheme.NoActionBar"
android:noHistory="true">
<intent-filter android:autoVerify="true">
<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="someHost" />
</intent-filter>
</activity>
Handle the intent data from the onboarding URL Next button in the Activity onCreate
method:
Uri uri = getIntent().getData();
if (uri != null) {
AppConfig appConfig = AppConfig.createAppConfigFromString(uri.toString(), null);
FlowContext flowContext = new FlowContextBuilder()
.setApplication(appConfig)
.build();
Flow.start(this, flowContext, (requestCode, resultCode, data) -> {
if (resultCode == RESULT_CANCELED) {
...
} else if (resultCode == RESULT_OK) {
...
}
return null;
});
}
val uri = intent.data
if (uri != null) {
val appConfig = AppConfig.createAppConfigFromString(uri.toString(), null)
val flowContext = FlowContextBuilder()
.setApplication(appConfig)
.build()
Flow.start(this, flowContext) { _, resultCode, _ ->
if (resultCode == RESULT_CANCELED) {
...
} else if (resultCode == RESULT_OK) {
...
}
}
}
Token Introspect and Renew¶
In some cases, client apps may want to check the expiration times of OAuth tokens. The SDK can automatically handle the renewal of OAuth tokens after they expire. However, this may interrupt end-user actions and negatively affect the user experience. The new APIs getAccessTokenExpirationTime
and getRefreshTokenExpirationTime
are provided in the class OAuth2Helper
for client apps.
Tip
Due to server restrictions, the SDK cannot get the expiration time of refresh token if OAuth is not configured with IAS Service.
If client app decides to proactively renew the OAuth tokens before they actually expire, it can call the new API renewOAuth2Token
. This API provides an option to force renew both the OAuth access token and refresh token.
Example:
val accessTime = OAuth2Helper.getAccessTokenExpirationTime(url)
val refreshTime = OAuth2Helper.getRefreshTokenExpirationTime(url)
// if access token expires in less than 60 seconds
if (accessTime?.isBefore(Instant.now().plusSeconds(60)) == true) {
// try to renew the access token by refresh token
OAuth2Helper.renewOAuth2Token(url, false)
}
// if refresh token expires in less than 60 seconds
if (refreshTime?.isBefore(Instant.now().plusSeconds(60)) == true) {
// try to renew both the access token and refresh token
OAuth2Helper.renewOAuth2Token(url, true)
}
SSL Client Certificate¶
It is possible to handle client certificate challenges by using the SslClientAuth
class to create a custom
SSLSocketFactory
and X509TrustManager
for the OKHttpClient
.
This provides an implementation that uses system certificates for the challenges. If you require a different source for
certificates, you can implement a custom CertificateProvider
and provide this to the constructor.
Example:
SslClientAuth sslClientAuth = SslClientAuth.system();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslClientAuth.getSslSocketFactory(), sslClientAuth.getTrustManager())
.addInterceptor(new CertificateFailureInterceptor(sslClientAuth))
.build();
val sslClientAuth = SslClientAuth.system()
val okHttpClient = OkHttpClient.Builder()
.sslSocketFactory(sslClientAuth.sslSocketFactory, sslClientAuth.trustManager)
.addInterceptor(CertificateFailureInterceptor(sslClientAuth))
.build()
Tip
Use the CertificateFailureInterceptor
to handle connection reset errors that can occur while the request is
waiting for a certificate from the provider.
Rejected Certificates¶
If a certificate is rejected, the server returns an "HTTP 403 Forbidden status" error. The client code should clear the current certificate and reset the connection pool.
Example:
Response response = okHttpClient.newCall(request).execute();
if (response.code() == 403) {
HttpUrl url = response.request().url();
sslClientAuth.clear(url.host(), url.port());
okHttpClient.connectionPool().evictAll();
}
val response = okHttpClient.newCall(request).execute()
if (response.code() == 403) {
val url = response.request().url()
sslClientAuth.clear(url.host(), url.port())
okHttpClient.connectionPool().evictAll()
}
Certificate Provider¶
The CertificateProvider
can be used to provide client certificates (X.509 certificate) to authenticate against the
server. This interface is used with SSL client certificate authentication and web view-based authentication, such as SAML
and OAuth2.
Included in the SDK is the SystemCertificateProvider
for getting the certificate from the system. If you require
another source for certificates, you can create a custom implementation of the CertificateProvider
.
Clearing Client Certificates¶
Certificate-Based Authentication¶
Call the clear
method on the SslClientAuth
instance used.
WebView
¶
Call the clear
method on the CertificateProvider
used with the WebView
.
Note
The web view also remembers the certificate. To forget the certificate in the WebView
you must also call
WebView.clearClientCertPreferences(null);
Custom Tabs/External Browser¶
It is outside the application's control to clear/forget the certificate from an external browser. The user of the device will need to forget the browsing data in the browser and restart it.
API Key Only¶
The API key only authentication method is introduced in SAP BTP SDK for Android version 3.4 for applications on SAP Business Technology Platform, Cloud Foundry environment. To use this type of authentication to access 'SAP Mobile Services', the client code can do the following:
val s = ...
val appConfig = AppConfig.createAppConfigFromJsonString(s)
val parameter = SettingsParameters(
appConfig.serviceUrl,
appConfig.appID,
Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID),
appConfig.applicationVersion
).apply {
SettingsProvider.set(this)
}
val httpClient = OkHttpClient.Builder()
.addInterceptor(AppHeadersInterceptor(parameter))
.addInterceptor(APIKeyInterceptor("apikey"))
.cookieJar(WebkitCookieJar())
.build()
ClientProvider.set(httpClient)
when (val result = UserService().retrieveUser()) {
is ServiceResult.SUCCESS -> {
val user = result.data as User
assertTrue(user.id.contains("anonymous"))
}
is ServiceResult.FAILURE -> {
//Failure
}
}
AppConfig appConfig = AppConfig.createAppConfigFromJsonString("....");
SettingsParameters sp = new SettingsParameters(appConfig.getServiceUrl(),
appConfig.getAppID(), "device id", appConfig.getApplicationVersion());
SettingsProvider.set(sp);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new AppHeadersInterceptor(sp))
.addInterceptor(new APIKeyInterceptor("apikey"))
.cookieJar(new WebkitCookieJar())
.build();
ClientProvider.set(client);
new UserService().retrieveUser(new ServiceListener<User>() {
@Override
public void onServiceDone(@NotNull ServiceResult<User> serviceResult) {
//user information in serviceResult
}
});
If the client code is using the flows component to handle the onboarding process, it only needs to pass the API key into SDKInitializer
and leave the preceding logic to the flows component. Subsequently, after the onboarding or restore flow, SettingsParameter
and OKHttpClient
will be ready and the services can be used directly.
//in onCreate of Application
SDKInitializer.start(
this,
apiKey = "apikey",
services = services.toTypedArray()
)
SDKInitializer.INSTANCE.start(this, services.toArray(new MobileService[0]), "apikey");
If the client code is using the SAP BTP SDK for Android version 3.3 flows component in the callback function of onOkHttpClientReady
of FlowStateListener
, the interceptor needs to be added into OkHttpClient
explicitly:
class MyFlowStateListener : FlowStateListener() {
override fun onOkHttpClientReady(httpClient: OkHttpClient) {
val interceptor = object : okhttp3.Interceptor {
override fun intercept(chain: okhttp3.Interceptor.Chain): Response {
val request: Request = chain.request()
val newRequest: Request = request.newBuilder()
.header(
ClientProvider.HTTP_HEADER_X_API_KEY,
"apiKey"
)
.build()
return chain.proceed(newRequest)
}
}
httpClient.addUniqueInterceptor(interceptor)
}
}
@Override
public void onOkHttpClientReady(@NotNull OkHttpClient httpClient) {
//With the last parameter as 'true', 'client' will be saved back into ClientProvider
//after adding the interceptor
OkHttpClient client = SDKUtils.addUniqueInterceptor(httpClient, chain -> {
Request request = chain.request();
Request newRequest = request.newBuilder()
.header(ClientProvider.HTTP_HEADER_X_API_KEY, "apikey")
.build();
return chain.proceed(newRequest);
}, true);
//set other properties
OkHttpClient newClient = client.newBuilder()
.connectTimeout(1000, TimeUnit.MICROSECONDS)
.callTimeout(500, TimeUnit.MICROSECONDS)
.build();
//set it into ClientProvider, this is required if new properties are needed
//and 'newClient' will be used for API calls after this point in other places.
ClientProvider.set(newClient);
}
Logout¶
To log out a user, use the following APIs in UserService
:
fun logoutUser(listener: ServiceListener<Boolean>)
suspend fun logoutUser(): ServiceResult<Boolean>
Warning
You must send the logout request with the same cookieJar
instance you used for authentication. The logout
API on the mobile service expects session cookies. Without them, the logout is ineffective.
Example:
UserService service = new UserService();
service.logoutUser(serviceResult -> {
Intent intent = new Intent(this, WelcomeActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
});
MainScope().launch {
UserService().logoutUser()
}.invokeOnCompletion {
Intent(this, WelcomeActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(this)
}
}
See Logout Service for more information.
User Session Check¶
When the user session finishes (SAML), or the user token expires (OAuth), network requests to the server will be challenged
and authentication UI will be shown to the user.
UserSessionService
can check and block requests when the user id has changed after authentication has successfully been completed again.
UserSessionService
has one callback function as a parameter, which allows client code to add its own logic in this scenario.
List<MobileService> services = new ArrayList<>();
services.add(new UserSessionService(() -> {
Log.d(TAG, "User session is changed unexpectedly.");
return null;
}));
SDKInitializer.INSTANCE.start(this, services.toArray(new MobileService[0]));
val services = mutableListOf<MobileService>()
services.add(UserSessionService {
Log.d(TAG, "User session is changed unexpectedly.")
})
SDKInitializer.start(this, * services.toTypedArray())
Network Requests From the Background¶
When the application is in the background, UI will not be shown to authenticate the user. A background application can be destroyed at any time, and any ongoing network requests will also be stopped. There are two options for reliably completing network requests while the application is in the background: foreground services and the Job Scheduler.
A network request running from a foreground service or a scheduled job while the application is not running in the foreground will not be able to show UI to authenticate the user. However, authentication can still succeed if it doesn't require UI - for example, if a valid OAuth token is available.
Foreground Service¶
A foreground service will only be stopped by the Android operating system under severe resource constraints. The foreground service is therefore a good choice if a network request must be done immediately, even if the application is in the background.
A foreground service must have some UI in the form of a status bar icon.
Job Scheduler¶
Using Job Scheduler is more suited to tasks that don't need to be done immediately, but should be executed when certain requirements are met, such as when an unmetered internet connection is available. Compared to a foreground service, there is less control over when a job will be executed, however no UI needs to be implemented to use the Job Scheduler.
Preventing Authentication UI¶
Applications might make requests that are not critical to the application flow and that are unrelated to what the user is doing in the app at that moment. In that case, if a request gets an authentication challenge requiring UI to resolve, you may prefer to have the request fail rather than interrupt the user with authentication UI.
Global Switch¶
This can be controlled by the app by implementing AuthenticationUiCallback
and registering the callback on the
AuthenticationUiCallbackManager
class. By default, authentication UI is always allowed to be shown. Calling
AuthenticationUiCallbackManager.setAuthenticationUICallback
with null
will restore the default behavior.
Example:
AuthenticationUiCallbackManager.setAuthenticationUiCallback(new AuthenticationUiCallback() {
@Override
public boolean allowShowingUiToAuthenticate() {
if (isUserReadyForUI()) {
// The user isn't doing anything that can't be interrupted. Return true to
// allow the authentication UI to be shown.
return true;
}
return false;
}
});
AuthenticationUiCallbackManager.setAuthenticationUiCallback {
if (isUserReadyForUI()) {
// The user isn't doing anything that can't be interrupted. Return true to
// allow the authentication UI to be shown.
true
} else false
}
Silent OkHttpClient
and Request
¶
With the above approach, your requests will not be able to bring up the authentication UI, which might not be ideal. We have introduced two new APIs that allow the client code to control this at either the OkHttpClient
or Request
levels.
The following API creates a silent OkHttpClient
. Requests made using this OkHttpClient
cause the authentication UI to not be invoked.
fun OkHttpClient.silent(): OkHttpClient { ... }
If you want to make a request silent, you can get a Call
from the API below, then execute it.
fun OkHttpClient.newSilentCall(request: Request): Call { .... }
For example:
val request = ...
ClientProvider.get().newSilentCall(request).execute().use { response ->
//process the response
}
Note
These two APIs only work for SAML and OAuth2 authentications.