Skip to content

Authentication

Basic Auth Configuration with In-memory Credential Store

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor =
        new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());//globally set
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

// Uses built-in memory store for storing credentials
ClientProvider.set( new OkHttpClient.Builder()
    .connectionSpecs(connectionSpecs)
    .addInterceptor(appHeadersInterceptor)
    .authenticator(new BasicAuthDialogAuthenticator())
    .cookieJar(new WebkitCookieJar())
    .build());
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())//globally set
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

// Uses built-in memory store for storing credentials
ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .authenticator(BasicAuthDialogAuthenticator())
        .cookieJar(WebkitCookieJar())
        .build())

Basic Auth Configuration with Persistent Credential Store

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor =
        new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

// PersistentCredentialStore is a custom implementation of BasicAuthCredentialStore that
// uses secure store for storing credentials.
PersistentCredentialStore basicAuthPersistentStore = new PersistentCredentialStore();
ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .authenticator(new BasicAuthDialogAuthenticator(basicAuthPersistentStore))
        .cookieJar(new WebkitCookieJar())
        .build());

// Remember to close the store when application is closed
basicAuthPersistentStore.close();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

// PersistentCredentialStore is a custom implementation of BasicAuthCredentialStore that
// uses secure store for storing credentials.
val basicAuthPersistentStore = PersistentCredentialStore()
ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .authenticator(BasicAuthDialogAuthenticator(basicAuthPersistentStore))
        .cookieJar(WebkitCookieJar())
        .build())

// Remember to close the store when application is closed
basicAuthPersistentStore.close()

SAML Auth Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor = new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(new SamlInterceptor(CpmsParameters.getSettingsParameters()))
        .cookieJar(new WebkitCookieJar())
        .build());
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(SamlInterceptor(CpmsParameters.getSettingsParameters()))
        .cookieJar(WebkitCookieJar())
        .build())

SAML Auth Configuration Using Auth URL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor = new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(new SamlInterceptor(
                new SamlConfiguration.Builder().authUrl(
                        CpmsParameters.getSettingsParameters().getBackendUrl()
                                + "/SAMLAuthLauncher").build()))
        .cookieJar(new WebkitCookieJar())
        .build());
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(SamlInterceptor(
                SamlConfiguration.Builder().authUrl(
                        CpmsParameters.getSettingsParameters().backendUrl + "/SAMLAuthLauncher").build()))
        .cookieJar(WebkitCookieJar())
        .build())

SAML Auth Configuration Using WebView

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor = new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(new SamlInterceptor(new SamlWebViewProcessor(
                new SamlConfiguration.Builder().authUrl(CpmsParameters.getSettingsParameters().getBackendUrl()
                        + "/SAMLAuthLauncher").build())))
        .cookieJar(new WebkitCookieJar())
        .build());
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(SamlInterceptor(SamlWebViewProcessor(
                SamlConfiguration.Builder().authUrl(CpmsParameters.getSettingsParameters().backendUrl + "/SAMLAuthLauncher").build())))
        .cookieJar(WebkitCookieJar())
        .build())

SAML Auth Configuration for Certificate-Based Authentication

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor = new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(new SamlInterceptor(new SamlWebViewProcessor(
                new SamlConfiguration.Builder().authUrl(CpmsParameters.getSettingsParameters().getBackendUrl()
                        + "/SAMLAuthLauncher").build(),
                //System certificate provider presents a dialog to select a certificate
                new SystemCertificateProvider())))
        .cookieJar(new WebkitCookieJar())
        .build());
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(SamlInterceptor(SamlWebViewProcessor(
                SamlConfiguration.Builder().authUrl(CpmsParameters.getSettingsParameters().backendUrl + "/SAMLAuthLauncher").build(),
                //System certificate provider presents a dialog to select a certificate
                SystemCertificateProvider())))
        .cookieJar(WebkitCookieJar())
        .build())

OAuth Configuration for Authorization Code Flow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor =
        new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

// Use Chrome custom tabs or built-in white-listed browsers to process oauth flow
// ClientId and Url values are displayed in Mobile Services cockpit
// (Application security page, click edit for details)
OAuth2BrowserProcessor browserProcessor = new OAuth2BrowserProcessor(
        new OAuth2Configuration.Builder()
                .clientId("48417b17-685d-44d7-8377-4b4030d6be33")
                .authUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/authorize")
                .redirectUrl("com.sap.template://oauthasservices-youraccountid.hana.ondemand.com")
                .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
                .responseType(OAuth2Configuration.CODE_RESPONSE_TYPE)
                .build());

/*
Following entry must be added to app AndroidManifest.xml to match the redirectUrl scheme above
<activity android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity">
    <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.sap.template" />
    </intent-filter>
</activity>
*/

/*
// OR, use Webview as the browser to process oauth flow
// ClientId and Url values are displayed in Mobile Services cockpit
// (Application security page, click edit for details)
OAuth2WebViewProcessor webviewProcessor = new OAuth2WebViewProcessor(
    new OAuth2Configuration.Builder()
        .clientId("48417b17-685d-44d7-8377-4b4040d6be33")
        .authUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/authorize")
        .redirectUrl("https://oauthasservices-youraccountid.hana.ondemand.com")
        .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
        .responseType(OAuth2Configuration.CODE_RESPONSE_TYPE)
        .build());

Following entry must be added to app AndroidManifest.xml to match the redirectUrl scheme and host above
<activity android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity">
    <intent-filter android:autoVerify="false">
        <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="oauthasservices-youraccountid.hana.ondemand.com"
            android:path="/networking_oauth_https_redirect/oauth2redirect" />
    </intent-filter>
</activity>
*/

// PersistentTokenStore is a custom implementation of OAuth2TokenStore that
// uses secure store for storing credentials.
PersistentTokenStore persistentTokenStore = new PersistentTokenStore();
ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(new OAuth2Interceptor(browserProcessor,
                persistentTokenStore)) //uses persistent token store
        .cookieJar(new WebkitCookieJar())
        .build());

// Remember to close the store when application is closed
persistentTokenStore.close();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

// Use Chrome custom tabs or built-in white-listed browsers to process oauth flow
// ClientId and Url values are displayed in Mobile Services cockpit
// (Application security page, click edit for details)
val browserProcessor = OAuth2BrowserProcessor(
        OAuth2Configuration.Builder()
                .clientId("48417b17-685d-44d7-8377-4b4030d6be33")
                .authUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/authorize")
                .redirectUrl("com.sap.template://oauthasservices-youraccountid.hana.ondemand.com")
                .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
                .responseType(OAuth2Configuration.CODE_RESPONSE_TYPE)
                .build())

/*
Following entry must be added to app AndroidManifest.xml to match the redirectUrl scheme above
<activity android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity">
    <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.sap.template" />
    </intent-filter>
</activity>
*/

/*
// OR, use Webview as the browser to process oauth flow
// ClientId and Url values are displayed in Mobile Services cockpit
// (Application security page, click edit for details)
val webviewProcessor = OAuth2WebViewProcessor(
        OAuth2Configuration.Builder()
                .clientId("48417b17-685d-44d7-8377-4b4040d6be33")
                .authUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/authorize")
                .redirectUrl("https://oauthasservices-youraccountid.hana.ondemand.com")
                .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
                .responseType(OAuth2Configuration.CODE_RESPONSE_TYPE)
                .build())

Following entry must be added to app AndroidManifest.xml to match the redirectUrl scheme and host above
<activity android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity">
    <intent-filter android:autoVerify="false">
        <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="oauthasservices-youraccountid.hana.ondemand.com"
            android:path="/networking_oauth_https_redirect/oauth2redirect" />
    </intent-filter>
</activity>
*/

// PersistentTokenStore is a custom implementation of OAuth2TokenStore that
// uses secure store for storing credentials.
val persistentTokenStore = PersistentTokenStore()
ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(OAuth2Interceptor(browserProcessor,
                persistentTokenStore)) //uses persistent token store
        .cookieJar(WebkitCookieJar())
        .build())

// Remember to close the store when application is closed
persistentTokenStore.close()

OAuth Configuration Using White-Listed Browser

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor =
        new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);

// Use chrome only (as a white-listed) browser to process oauth flow
// ClientId and Url values are displayed in Mobile Services cockpit
// (Application security page, click edit for details)
ArrayList<BrowserDetails> allowedBrowsers = new ArrayList<>();
allowedBrowsers.add(BrowserDetails.chrome());//Add other browsers as you like to allow
OAuth2Configuration oAuth2Configuration = new OAuth2Configuration.Builder()
        .clientId("48417b17-685d-44d7-8377-4b4030d6be33")
        .authUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/authorize")
        .redirectUrl("com.sap.template://oauthasservices-youraccountid.hana.ondemand.com")
        .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
        .responseType(OAuth2Configuration.CODE_RESPONSE_TYPE)
        .build();
OAuth2BrowserProcessor chromeProcessor = new OAuth2BrowserProcessor(
        oAuth2Configuration,
        null,
        new BrowserWhitelist(allowedBrowsers));
/*
Following entry must be added to app AndroidManifest.xml to match the redirectUrl scheme above
<activity android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity">
    <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.sap.template" />
    </intent-filter>
</activity>
*/

// PersistentTokenStore is a custom implementation of OAuth2TokenStore that
// uses secure store for storing credentials.
TokenPersistentStore tokenPersistentStore = new TokenPersistentStore();
ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .cookieJar(new WebkitCookieJar())
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(new OAuth2Interceptor(chromeProcessor,
                tokenPersistentStore)) //uses persistent token store
        .build());

// Remember to close the store when application is closed
tokenPersistentStore.close();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)

// Use chrome only (as a white-listed) browser to process oauth flow
// ClientId and Url values are displayed in Mobile Services cockpit
// (Application security page, click edit for details)
val allowedBrowsers = ArrayList<BrowserDetails>()
allowedBrowsers.add(BrowserDetails.chrome())//Add other browsers as you like to allow
val oAuth2Configuration = OAuth2Configuration.Builder()
        .clientId("48417b17-685d-44d7-8377-4b4030d6be33")
        .authUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/authorize")
        .redirectUrl("com.sap.template://oauthasservices-youraccountid.hana.ondemand.com")
        .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
        .responseType(OAuth2Configuration.CODE_RESPONSE_TYPE)
        .build()
val chromeProcessor = OAuth2BrowserProcessor(
        oAuth2Configuration, null,
        BrowserWhitelist(allowedBrowsers))
/*
Following entry must be added to app AndroidManifest.xml to match the redirectUrl scheme above
<activity android:name="com.sap.cloud.mobile.foundation.authentication.OAuth2RedirectActivity">
    <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.sap.template" />
    </intent-filter>
</activity>
*/

// PersistentTokenStore is a custom implementation of OAuth2TokenStore that
// uses secure store for storing credentials.
val tokenPersistentStore = TokenPersistentStore()
ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .cookieJar(WebkitCookieJar())
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(OAuth2Interceptor(chromeProcessor,
                tokenPersistentStore)) //uses persistent token store
        .build())

// Remember to close the store when application is closed
tokenPersistentStore.close()

OAuth Configuration for Client Credentials Flow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());

AppHeadersInterceptor appHeadersInterceptor =
        new AppHeadersInterceptor(CpmsParameters.getSettingsParameters());
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
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);
//ClientId and Url values are displayed in Mobile Services cockpit (Application security page, click edit for details)
OAuth2Configuration credentialsGrantConfiguration = new OAuth2Configuration.Builder()
        .clientId("48417b17-685d-44d7-8377-4b4060d6be33")
        .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
        .responseType(OAuth2Configuration.GRANT_RESPONSE_TYPE)
        .secret("sdk#1")
        .build();

OAuth2TokenInMemoryStore oAuth2TokenInMemoryStore = new OAuth2TokenInMemoryStore();
// Configure OAuth2ClientCredentialGrantProcessor interceptor
ClientProvider.set( new OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(new OAuth2Interceptor(
                new OAuth2ClientCredentialGrantProcessor(credentialsGrantConfiguration),
                oAuth2TokenInMemoryStore)) //Uses in-memory token store
        .cookieJar(new WebkitCookieJar())
        .build());
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Note: Registering the ActivityLifecycleCallback should be done in your application's
// onCreate method.  AppLifecycleCallbackHandler must be registered for the authentication
// interceptors to show UI to authenticate the user.
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance())

val appHeadersInterceptor = AppHeadersInterceptor(CpmsParameters.getSettingsParameters())
// Only allow TLS 1.2 and 1.3 by using a custom list of ConnectionSpecs.
val connectionSpecs = LinkedList<ConnectionSpec>()
val strict = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
        .build()
connectionSpecs.add(strict)
//ClientId and Url values are displayed in Mobile Services cockpit (Application security page, click edit for details)
val credentialsGrantConfiguration = OAuth2Configuration.Builder()
        .clientId("48417b17-685d-44d7-8377-4b4060d6be33")
        .tokenUrl("https://oauthasservices-youraccountid.hana.ondemand.com/oauth2/api/v1/token")
        .responseType(OAuth2Configuration.CODE_RESPONSE_TYPE)
        .secret("sdk#1")
        .build()

val oAuth2TokenInMemoryStore = OAuth2TokenInMemoryStore()
// Configure OAuth2ClientCredentialGrantProcessor interceptor
ClientProvider.set(OkHttpClient.Builder()
        .connectionSpecs(connectionSpecs)
        .addInterceptor(appHeadersInterceptor)
        .addInterceptor(OAuth2Interceptor(
                OAuth2ClientCredentialGrantProcessor(credentialsGrantConfiguration),
                oAuth2TokenInMemoryStore)) //Uses in-memory token store
        .cookieJar(WebkitCookieJar())
        .build())

Disable Authentication Credentials Acquisition UI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Any subsequent challenge from the server will not show the UI till it has been changed.
// This may be needed when app UI context should not be switched for its impact on user
// experience. Note that the pending request that requires authentication
// will fail till credentials are acquired.
AuthenticationUiCallback doNotShowUI = new AuthenticationUiCallback() {
    @Override
    public boolean allowShowingUiToAuthenticate() {
        return false;
    }
};
AuthenticationUiCallbackManager.setAuthenticationUiCallback(doNotShowUI);

// Enable showing UI
AuthenticationUiCallback showUI = new AuthenticationUiCallback() {
    @Override
    public boolean allowShowingUiToAuthenticate() {
        return true;
    }
};
AuthenticationUiCallbackManager.setAuthenticationUiCallback(showUI);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Any subsequent challenge from the server will not show the UI till it has been changed.
// This may be needed when app UI context should not be switched for its impact on user
// experience. Note that the pending request that requires authentication
// will fail till credentials are acquired.
val doNotShowUI = AuthenticationUiCallback { false }
AuthenticationUiCallbackManager.setAuthenticationUiCallback(doNotShowUI)

// Enable showing UI
val showUI = AuthenticationUiCallback { true }
AuthenticationUiCallbackManager.setAuthenticationUiCallback(showUI)

File Certificate Provider Implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class MyFileCertificateProvider implements CertificateProvider {
    private static final Logger logger = LoggerFactory.getLogger(MyFileCertificateProvider.class);
    private static final String FILE_CERT_PASSWORD = "pass123#";
    private X509KeyManager x509KeyManager;
    private String alias;

    public MyFileCertificateProvider(Context context) {
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(context.getResources().openRawResource(R.raw.mycert),
                    FILE_CERT_PASSWORD.toCharArray());
            Enumeration<String> enumeration = keyStore.aliases();
            while (enumeration.hasMoreElements()) {
                String alias = enumeration.nextElement();
                Certificate[] certificate = keyStore.getCertificateChain(alias);
                if (certificate != null && certificate.length > 0
                        && certificate[0] instanceof X509Certificate) {
                    this.alias = alias;
                    break;
                }

            }
            KeyManagerFactory keyManagerFactory = KeyManagerFactory
                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, FILE_CERT_PASSWORD.toCharArray());
            for (KeyManager keyManager : keyManagerFactory.getKeyManagers()) {
                if (keyManager instanceof X509KeyManager) {
                    x509KeyManager = (X509KeyManager) keyManager;
                    break;
                }
            }
        } catch (Exception e) {
            logger.error("Exception occurred.." + e.getMessage());
        }
    }

    @Override
    public void clear(String host, int port) {
    }

    @Override
    public void clear() {
    }

    @Override
    public void onCertificateRequest(CertificateRequest request) {
        request.proceed(x509KeyManager.getPrivateKey(alias),
                x509KeyManager.getCertificateChain(alias));
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
internal class MyFileCertificateProvider(context: Context) : CertificateProvider {
    private var x509KeyManager: X509KeyManager? = null
    private var alias: String? = null

    init {
        try {
            val keyStore = KeyStore.getInstance("PKCS12")
            keyStore.load(
                context.resources.openRawResource(R.raw.mycert),
                FILE_CERT_PASSWORD.toCharArray()
            )
            val enumeration = keyStore.aliases()
            while (enumeration.hasMoreElements()) {
                val alias = enumeration.nextElement()
                val certificate = keyStore.getCertificateChain(alias)
                if (certificate != null && certificate.size > 0
                    && certificate[0] is X509Certificate
                ) {
                    this.alias = alias
                    break
                }

            }
            val keyManagerFactory = KeyManagerFactory
                .getInstance(KeyManagerFactory.getDefaultAlgorithm())
            keyManagerFactory.init(keyStore, FILE_CERT_PASSWORD.toCharArray())
            for (keyManager in keyManagerFactory.keyManagers) {
                if (keyManager is X509KeyManager) {
                    x509KeyManager = keyManager
                    break
                }
            }
        } catch (e: Exception) {
            logger.error("Exception occurred.." + e.message)
        }

    }

    override fun clear(host: String, port: Int) {}

    override fun clear() {}

    override fun onCertificateRequest(request: CertificateRequest) {
        request.proceed(
            x509KeyManager!!.getPrivateKey(alias),
            x509KeyManager!!.getCertificateChain(alias)
        )
    }

    companion object {
        private val logger = LoggerFactory.getLogger(MyFileCertificateProvider::class.java)
        private val FILE_CERT_PASSWORD = "pass123#"
    }
}

Basic Auth Credential Store Using Secure Store

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class PersistentCredentialStore implements BasicAuthCredentialStore {
    final String KEY_PREFIX = "key prefix";
    final String STORE_NAME = "secure store name";
    final Logger logger = LoggerFactory.getLogger(PersistentCredentialStore.class);
    SecureKeyValueStore mSecureStore;

    PersistentCredentialStore(Context context) {
        mSecureStore = new SecureKeyValueStore(context, STORE_NAME);
        try {
            mSecureStore.open(null);// A key is generated and stored in Android KeyStore
        } catch (OpenFailureException e) {
            logger.error("Store already exists with non-default key for no passcode!", e);
        }
    }

    @Override
    public void storeCredential(String host, String realm, String[] credentials) {
        mSecureStore.put(key(host, realm), credentials);
    }

    @Override
    public String[] getCredential(String host, String realm) {
        return mSecureStore.getSerializable(key(host, realm));
    }

    @Override
    public void deleteCredential(String host, String realm) {
        mSecureStore.remove(key(host, realm));
    }

    @Override
    public void deleteAllCredentials() {
        mSecureStore.removeAll();
    }

    private String key(String host, String realm) {
        return KEY_PREFIX + host + "::" + realm;
    }

    public void close() {
        mSecureStore.close();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
internal class PersistentCredentialStore(context: Context) : BasicAuthCredentialStore {
    val KEY_PREFIX = "key prefix"
    val STORE_NAME = "secure store name"
    val logger = LoggerFactory.getLogger(PersistentCredentialStore::class.java)
    var mSecureStore: SecureKeyValueStore

    init {
        mSecureStore = SecureKeyValueStore(context, STORE_NAME)
        try {
            mSecureStore.open(null)// A key is generated and stored in Android KeyStore
        } catch (e: OpenFailureException) {
            logger.error("Store already exists with non-default key for no passcode!", e)
        }

    }

    override fun storeCredential(host: String, realm: String, credentials: Array<String>) {
        mSecureStore.put(key(host, realm), credentials)
    }

    override fun getCredential(host: String, realm: String): Array<String> {
        return mSecureStore.getSerializable(key(host, realm))
    }

    override fun deleteCredential(host: String, realm: String) {
        mSecureStore.remove(key(host, realm))
    }

    override fun deleteAllCredentials() {
        mSecureStore.removeAll()
    }

    private fun key(host: String, realm: String): String {
        return "$KEY_PREFIX$host::$realm"
    }

    fun close() {
        mSecureStore.close()
    }
}

OAuth Token Store Using Secure Store

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class PersistentTokenStore implements OAuth2TokenStore {
    final String KEY_PREFIX = "key prefix";
    final String STORE_NAME = "secure store name";
    final Logger logger = LoggerFactory.getLogger(PersistentTokenStore.class);
    SecureKeyValueStore mSecureStore;

    PersistentTokenStore(Context context) {
        mSecureStore = new SecureKeyValueStore(context, STORE_NAME);
        try {
            mSecureStore.open(null);// A key is generated and stored in Android KeyStore
        } catch (OpenFailureException e) {
            logger.error("Store already exists with non-default key when trying to skip passcode!", e);
        }
    }

    @Override
    public void storeToken(OAuth2Token oauth2Token, String url) {
        mSecureStore.put(tokenKey(url), oauth2Token);
    }

    @Override
    public OAuth2Token getToken(String url) {
        return mSecureStore.getSerializable(tokenKey(url));
    }

    @Override
    public void deleteToken(String url) {
        mSecureStore.remove(tokenKey(url));
    }

    private String tokenKey(String url) {
        return KEY_PREFIX + Uri.parse(url).getHost();
    }

    public void close() {
        mSecureStore.close();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
internal class PersistentTokenStore(context: Context) : OAuth2TokenStore {
    val KEY_PREFIX = "key prefix"
    val STORE_NAME = "secure store name"
    val logger = LoggerFactory.getLogger(PersistentTokenStore::class.java)
    var mSecureStore: SecureKeyValueStore

    init {
        mSecureStore = SecureKeyValueStore(context, STORE_NAME)
        try {
            mSecureStore.open(null)// A key is generated and stored in Android KeyStore
        } catch (e: OpenFailureException) {
            logger.error("Store already exists with non-default key when trying to skip passcode!", e)
        }

    }

    override fun storeToken(oauth2Token: OAuth2Token, url: String) {
        mSecureStore.put(tokenKey(url), oauth2Token)
    }

    override fun getToken(url: String): OAuth2Token {
        return mSecureStore.getSerializable(tokenKey(url))
    }

    override fun deleteToken(url: String) {
        mSecureStore.remove(tokenKey(url))
    }

    private fun tokenKey(url: String): String {
        return KEY_PREFIX + Uri.parse(url).host
    }

    fun close() {
        mSecureStore.close()
    }
}