Skip to content

Authentication

The Authentication module allows you to configure your mobile application to access SAP Cloud Platform Mobile Services using the following authentication methods:

  • Basic
  • SAML
  • OAuth2
  • Certificate

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:

1
2
3
4
5
6
7
public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());
    }
}
1
2
3
4
5
6
class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())
    }
}

Make sure to add the custom Application to the AndroidManifest.xml as follows:

1
2
3
4
5
<manifest . . . >
    <application android:name=".SampleApplication" . . . >
        . . .
    </application>
</manifest>

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 handing see Handling authentication in the OKHttp wiki.

One Time Passcode

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

If the OtpConfiguration.Builder is constructed with a SettingsParameters object, then 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 explicitly set.

1
2
3
4
5
6
7
8
9
// Create the OtpConfiguration using a SettingsParameters object
OtpConfiguration otpConfig = new OtpConfiguration.Builder(settingsParameters).build();

// Add an OtpInterceptor to the OkHttpClient to handle the OTP challenges.
OkHttpClient samlWithOtpHttpClient = new OkHttpClient.Builder()
                .addInterceptor(new SamlInterceptor(settingsParameters))
                .addInterceptor(new OtpInterceptor(otpConfig))
                .cookieJar(new WebkitCookieJar())
                .build();
1
2
3
4
5
6
7
8
9
// Create the OtpConfiguration using a SettingsParameters object
val otpConfig = OtpConfiguration.Builder(settingsParameters).build()

// Add an OtpInterceptor to the OkHttpClient to handle the OTP challenges.
val samlWithOtpHttpClient = OkHttpClient.Builder()
                .addInterceptor(SamlInterceptor(settingsParameters))
                .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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<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:

1
com.example.myapp.xcallbackurl://x-callback-url/setCredential?username_paramvalue=[username]&amp;passcode_paramvalue=[passcode]

replacing com.example.myapp with your app ID.

SAML 2.0

General Usage

The SamlInterceptor class is capable of detecting SAML challenges in the network requests response and starting the SAML authentication flow.

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 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();
1
2
3
4
5
6
7
8
9
val samlConfiguration = SamlConfiguration.Builder()
    .authUrl("https://hcpms-xxxxxxxtrial.hanatrial.ondemand.com/SAMLAuthLauncher")
    .build()

// Create an OKHttp intercepter to listen for challenges
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(SamlInterceptor(samlConfiguration))
    .cookieJar(WebkitCookieJar())
    .build()

Warning

The WebkitCookieJar is required to make the cookies from SAML webflow available to subsequent network requests.

Preauthentication

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 shown 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 SAMLProcessor's authenticate method.

Example:

1
2
3
4
5
6
7
8
SamlProcessor samlProcessor = new SamlWebViewProcessor(samlConfiguration);

try {
    samlProcessor.authenticate();
} catch (IOException ex) {
    // If there was an error during the authentication process
    // or it was cancelled.
}
1
2
3
4
5
6
7
8
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

By default the WebView will ignore certificate challenges. To handle certificate challenges with the WebView, construct the SamlWebViewProcessor with a CertificateProvider such as SystemCertificateProvider.

OAuth 2.0

General Usage

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 the 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 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);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<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>

Preauthentication

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 appear 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 OAuth2Processor's authenticate method.

Example:

1
2
3
4
5
6
7
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.
}
1
2
3
4
5
6
7
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

If OAuth2BrowserProcessor is used, some browsers, including Chrome, ask for the user to select a certificate from the system keychain when a client certificate challenge is encountered. When using a WebView processor, it will by default ignore certificate challenges. To handle certificate challenges with the WebView you must construct the OAuth2WebViewProcessor with a CertificateProvider such as SystemCertificateProvider.

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 implemention 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:

1
2
3
4
5
SslClientAuth sslClientAuth = SslClientAuth.system();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .sslSocketFactory(sslClientAuth.getSslSocketFactory(), sslClientAuth.getTrustManager())
    .addInterceptor(new CertificateFailureInterceptor(sslClientAuth))
    .build();
1
2
3
4
5
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:

1
2
3
4
5
6
Response response = okHttpClient.newCall(request).execute();
if (response.code() == 403) {
    HttpUrl url = response.request().url();
    sslClientAuth.clear(url.host(), url.port());
    okHttpClient.connectionPool().evictAll();
}
1
2
3
4
5
6
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 WebView-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 webview 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.

Logout

The SDK does not include an API to log out users. Instead, the application developer must create and send a simple HTTP request to the SAP Cloud Platform mobile service backend. The logout API URL on the mobile service is: http[s]:// SAPcpms base URL /mobileservices/session/logout

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:

1
2
3
4
5
6
Request request = new Request.Builder()
    .post(RequestBody.create(null, ""))
    .url("https://<#SAPcpms base URL#>/mobileservices/sessions/logout")
    .build();

okHttpClient.newCall(request).enqueue(responseCallback);
1
2
3
4
5
6
val request = Request.Builder()
    .post(RequestBody.create(null, ""))
    .url("https://<#SAPcpms base URL#>/mobileservices/sessions/logout")
    .build()

okHttpClient.newCall(request).enqueue(responseCallback)

See Logout Service for more information.

Network Requests from the Background

When the application is in the background, UI will not be shown to authenticate the user. A backgrounded 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 at all to use the Job Scheduler.

Preventing Authentication UI

Applications might make requests that are not critical to the application flow and which 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.

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

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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;
    }
});
1
2
3
4
5
6
7
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
}