Performing resource calls
Starting from version 6.04.00, the Android SDK exposes instances of OkHttp 3 clients that are prepared for making secure resource calls to your backend. The instances are already pre-configured and secured by the Android SDK, so that you do not need to worry about encryption or decrypting the payload or certificate pinning. The client can be used later with libraries like Retrofit 1.X and 2.X.
-
DeviceClient#getOkHttpClient()
returns an instance of theOkHttpClient
with security features like certificate pinning, but no user or device authentication. -
DeviceClient#getAnonymousResourceOkHttpClient()
returns an instance of the secured client that also uses device credentials for authentication. -
UserClient#getResourceOkHttpClient()
returns an instance of the secured client that also uses the user's credentials for authentication.
A high level overview of the workflow:
- APP > SDK: Build REST adapter-based, secured client provided by the Android SDK.
- SDK > resource gateway: Request a resource at the resource gateway.
- Resource gateway > IDAAS-core: Validate the provided token.
- IDAAS-core > resource gateway: Details regarding the access token like scope and user, which will be verified by the resource gateway.
- Resource gateway > resource server: Get the resource.
- Resource server > resource gateway: Return the resource.
- Resource gateway > app: Return the resource.
Using OkHttp 3 client with Retrofit 1.X and 2.X
The OkHttp 3 clients that are returned by the Android SDK (DeviceClient#getOkHttpClient()
, DeviceClient#getAnonymousResourceOkHttpClient()
, UserClient#getResourceOkHttpClient()
, UserClient#getImplicitResourceOkHttpClient()
, DeviceClient#getUnauthenticatedResourceOkHttpClient()
) can be used with both Retrofit 1.X and 2.X.
In Retrofit 2.X, the Retrofit.Builder()
expects that an instance of the OkHttpClient
will be provided with the client method:
private static <T> T prepareSecuredRetrofitClient(final Class<T> clazz, final Context context, final OkHttpClient okHttpClient) {
final OneginiClient oneginiClient = OneginiSDK.getOneginiClient(context);
final Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
// ...
.build();
return retrofit.create(clazz);
}
In Retrofit 1.X, you need to use additional dependency called retrofit1-okhttp3-client. Then the client can be set with:
private static <T> T prepareSecuredRetrofitClient(final Class<T> clazz, final Context context, final OkHttpClient okHttpClient) {
final OneginiClient oneginiClient = OneginiSDK.getOneginiClient(context);
final RestAdapter restAdapter = new RestAdapter.Builder()
.setClient(new Ok3Client(okHttpClient))
// ...
.build();
return restAdapter.create(clazz);
}
We recommend using Retrofit 2.X. The following examples will only cover usage of Retrofit 2.X.
Fetching resources anonymously
A device can use its OAuth credentials to obtain an access token object, which in turn can be used for proving its authenticity. An anonymous resource call can be typically used in cases where a user does not need to log in or register to use certain functionality or access some resource.
Authenticating the device
It is your responsibility to perform device authentication with DeviceClient#authenticateDevice
whenever the resource gateway responds with the 401 unauthorized status code. After the device has been authenticated using DeviceClient#authenticateDevice
method, it is possible to start fetching resources.
Example code for authenticating the device
private void authenticateDevice() {
authenticatedUserProfile = OneginiSDK.getOneginiClient(this).getUserClient().getAuthenticatedUserProfile();
OneginiSDK.getOneginiClient(this).getDeviceClient()
.authenticateDevice(new String[]{ "application-details" }, new OneginiDeviceAuthenticationHandler() {
@Override
public void onSuccess() {
callAnonymousResourceCallToFetchApplicationDetails();
}
@Override
public void onError(final OneginiDeviceAuthenticationError error) {
final @OneginiDeviceAuthenticationError.DeviceAuthenticationErrorType int errorType = error.getErrorType();
if (errorType == OneginiDeviceAuthenticationError.DEVICE_DEREGISTERED) {
onDeviceDeregistered();
} else if (errorType == OneginiDeviceAuthenticationError.USER_DEREGISTERED) {
onUserDeregistered(authenticatedUserProfile);
} else {
displayError(error);
}
}
}
);
}
Defining the REST interface
To fetch resources anonymously, you must define a REST interface. The path application-details
is relative to the endpoint that you need to configure on the HTTP client before making a call.
import com.onegini.mobile.exampleapp.model.ApplicationDetails;
import retrofit2.http.GET;
import io.reactivex.rxjava3.core.Single;
public interface AnonymousClient {
@GET("application-details")
Single<ApplicationDetails> getApplicationDetails();
}
Defining the REST adapter
Set up the retrofit instance by providing the anonymousClient (DeviceClient#getAnonymousResourceOkHttpClient())
resource gateway URL and REST interface.
In the examples below, the resource gateway URL (endpoint) is set explicitly on Retrofit.Builder
. Depending on your needs, you may set additional options, for example add a Gson
converter, or a RxJava3CallAdapterFactory
like in the example above. The client passed to the builder has to come from DeviceClient#getAnonymousResourceOkHttpClient()
.
import android.content.Context;
import com.onegini.mobile.exampleapp.model.ApplicationDetails;
import com.onegini.mobile.exampleapp.network.client.AnonymousClient;
import com.onegini.mobile.exampleapp.network.client.SecureResourceClient;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class AnonymousService {
private static AnonymousService INSTANCE;
public static AnonymousService getInstance(final Context context) {
if (INSTANCE == null) {
INSTANCE = new AnonymousService(context);
}
return INSTANCE;
}
private final AnonymousClient applicationDetailsRetrofitClient;
private AnonymousService(final Context context) {
applicationDetailsRetrofitClient = SecureResourceClient.prepareSecuredAnonymousRetrofitClient(AnonymousClient.class, context);
}
public Single<ApplicationDetails> getApplicationDetails() {
return applicationDetailsRetrofitClient.getApplicationDetails()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
import android.content.Context;
import com.onegini.mobile.exampleapp.OneginiSDK;
import com.onegini.mobile.sdk.android.client.OneginiClient;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
public class SecureResourceClient {
public static <T> T prepareSecuredAnonymousRetrofitClient(final Class<T> clazz, final Context context) {
final OkHttpClient okHttpClient = OneginiSDK.getOneginiClient(context).getDeviceClient().getAnonymousResourceOkHttpClient();
return prepareSecuredRetrofitClient(clazz, context, okHttpClient);
}
private static <T> T prepareSecuredRetrofitClient(final Class<T> clazz, final Context context, final OkHttpClient okHttpClient) {
final OneginiClient oneginiClient = OneginiSDK.getOneginiClient(context);
final Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(oneginiClient.getConfigModel().getResourceBaseUrl())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build();
return retrofit.create(clazz);
}
}
Performing the anonymous resource call
After the retrofit client is defined and instantiated, you can perform the resource call. The Android SDK will not process the response, which means that you need to handle it on your own. This also means that you need to make sure that the device has a valid access token by performing device authentication before making the anonymous resource call.
private void authenticateDevice() {
OneginiSDK.getOneginiClient(this).getDeviceClient()
.authenticateDevice(new String[]{ "application-details" }, new OneginiDeviceAuthenticationHandler() {
@Override
public void onSuccess() {
callAnonymousResourceCallToFetchApplicationDetails();
}
@Override
public void onError(final OneginiDeviceAuthenticationError error) {
final @OneginiDeviceAuthenticationError.DeviceAuthenticationErrorType int errorType = error.getErrorType();
if (errorType == OneginiDeviceAuthenticationError.DEVICE_DEREGISTERED) {
onDeviceDeregistered();
} else if (errorType == OneginiDeviceAuthenticationError.USER_DEREGISTERED) {
onUserDeregistered(authenticatedUserProfile);
} else {
displayError(error);
}
}
);
}
private void callAnonymousResourceCallToFetchApplicationDetails() {
disposables.add(
AnonymousService.getInstance(this)
.getApplicationDetails()
.subscribe(this::onApplicationDetailsFetched, throwable -> onApplicationDetailsFetchFailed())
);
}
private void onApplicationDetailsFetched(final ApplicationDetails applicationDetails) {
applicationDetailsTextView.setText(applicationDetails.getApplicationDetailsCombined());
}
private void onApplicationDetailsFetchFailed() {
applicationDetailsTextView.setText("Application details fetch failed");
}
@Override
public void onDestroy() {
disposables.clear();
super.onDestroy();
}
Fetching a resource on behalf of a user
To request a resource for a specific user, the client needs to be registered and the user's access token needs to be present within the application memory. The UserClient#authenticateUser
authentication flow must complete before a resource call can be made on behalf of the user.
Defining the REST interface
To fetch a resource, you must define a REST interface. The device path is relative to the endpoint, which must be configured on the HTTP client before making a call.
import com.onegini.mobile.exampleapp.network.response.DevicesResponse;
import retrofit2.http.GET;
import io.reactivex.rxjava3.core.Single;
public interface UserClient {
@GET("devices")
Single<DevicesResponse> getDevices();
}
Defining the REST adapter
Set up the retrofit instance by providing the resource client (UserClient#getResourceOkHttpClient()
), resource gateway URL and REST interface.
import android.content.Context;
import com.onegini.mobile.exampleapp.network.client.SecureResourceClient;
import com.onegini.mobile.exampleapp.network.client.UserClient;
import com.onegini.mobile.exampleapp.network.response.DevicesResponse;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class UserService {
private static UserService INSTANCE;
public static UserService getInstance(final Context context) {
if (INSTANCE == null) {
INSTANCE = new UserService(context);
}
return INSTANCE;
}
private final UserClient userRetrofitClient;
private UserService(final Context context) {
userRetrofitClient = SecureResourceClient.prepareSecuredUserRetrofitClient(UserClient.class, context);
}
public Single<DevicesResponse> getDevices() {
return userRetrofitClient.getDevices()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
import android.content.Context;
import com.onegini.mobile.exampleapp.OneginiSDK;
import com.onegini.mobile.sdk.android.client.OneginiClient;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
public class SecureResourceClient {
public static <T> T prepareSecuredUserRetrofitClient(final Class<T> clazz, final Context context) {
final OkHttpClient okHttpClient = OneginiSDK.getOneginiClient(context).getUserClient().getResourceOkHttpClient();
return prepareSecuredRetrofitClient(clazz, context, okHttpClient);
}
private static <T> T prepareSecuredRetrofitClient(final Class<T> clazz, final Context context, final OkHttpClient okHttpClient) {
final OneginiClient oneginiClient = OneginiSDK.getOneginiClient(context);
final Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(oneginiClient.getConfigModel().getResourceBaseUrl())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build();
return retrofit.create(clazz);
}
}
Performing the resource call
After the retrofit is defined and instantiated, you can proceed and perform the resource call itself. The Android SDK will not process the response, which means that you need to handle it on your own. This also means that you need to make sure that the user has a valid access token by triggering user authentication.
If the user's access token is available, the operation will succeed. If the resource gateway responds with 401 unauthorized
the access token is invalid.
private void fetchUserDevices() {
disposable = UserService.getInstance(this)
.getDevices()
.doFinally(this::onFetchComplete)
.subscribe(this::onDevicesFetched, throwable -> onDevicesFetchFailed());
}
private void onDevicesFetched(final DevicesResponse devicesResponse) {
displayFetchedDevices(devicesResponse.getDevices());
}
private void onDevicesFetchFailed() {
showToast("onDevicesFetchFailed");
}
private void onFetchComplete() {
progressBar.setVisibility(View.INVISIBLE);
}
private void displayFetchedDevices(final List<Device> devices) {
final DevicesAdapter adapter = new DevicesAdapter(devices);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
private void showToast(final String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void onDestroy() {
if (disposable != null) {
disposable.dispose();
}
super.onDestroy();
}
Fetching a resource on behalf of implicitly authenticated user
To request a resource for an implicitly authenticated user, the client needs to be registered and the user's implicit access token needs to be present within the application memory. You require the implicit UserClient#authenticateUserImplicitly
authentication flow to succeed before a resource call can be made on behalf of an implicitly authenticated user.
An implicit resource request should be used in cases where the usage of certain functionality or access to resources does not require a user to explicitly authenticate themself.
Defining the REST interface
To fetch a resource, you must define a REST interface. The user-id-decorated
path is relative to the endpoint that you need to configure on the HTTP client before making a call.
import com.onegini.mobile.exampleapp.model.ImplicitUserDetails;
import retrofit2.http.GET;
import io.reactivex.rxjava3.core.Single;
public interface ImplicitUserClient {
@GET("user-id-decorated")
Single<ImplicitUserDetails> getImplicitUserDetails();
}
Defining the REST adapter
Set up the retrofit instance by providing the UserClient#getImplicitResourceOkHttpClient()
resource client, resource gateway URL, and REST interface.
import com.google.gson.annotations.SerializedName;
public class ImplicitUserDetails {
@SerializedName("decorated_user_id")
private String decoratedUserId;
@Override
public String toString() {
return decoratedUserId;
}
}
import android.content.Context;
import com.onegini.mobile.exampleapp.model.ImplicitUserDetails;
import com.onegini.mobile.exampleapp.network.client.ImplicitUserClient;
import com.onegini.mobile.exampleapp.network.client.SecureResourceClient;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class ImplicitUserService {
private static ImplicitUserService INSTANCE;
public static ImplicitUserService getInstance(final Context context) {
if (INSTANCE == null) {
INSTANCE = new ImplicitUserService(context);
}
return INSTANCE;
}
private final ImplicitUserClient applicationDetailsRetrofitClient;
private ImplicitUserService(final Context context) {
applicationDetailsRetrofitClient = SecureResourceClient.prepareSecuredImplicitUserRetrofitClient(ImplicitUserClient.class, context);
}
public Single<ImplicitUserDetails> getImplicitUserDetails() {
return applicationDetailsRetrofitClient.getImplicitUserDetails()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
import android.content.Context;
import com.onegini.mobile.exampleapp.model.ImplicitUserDetails;
import com.onegini.mobile.exampleapp.network.client.ImplicitUserClient;
import com.onegini.mobile.exampleapp.network.client.SecureResourceClient;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class ImplicitUserService {
private static ImplicitUserService INSTANCE;
public static ImplicitUserService getInstance(final Context context) {
if (INSTANCE == null) {
INSTANCE = new ImplicitUserService(context);
}
return INSTANCE;
}
private final ImplicitUserClient applicationDetailsRetrofitClient;
private ImplicitUserService(final Context context) {
applicationDetailsRetrofitClient = SecureResourceClient.prepareSecuredImplicitUserRetrofitClient(ImplicitUserClient.class, context);
}
public Single<ImplicitUserDetails> getImplicitUserDetails() {
return applicationDetailsRetrofitClient.getImplicitUserDetails()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
Performing the implicit resource call
When the retrofit is defined and instantiated, you can perform the resource call. The Android SDK will not process the response, which means that you need to handle it on your own. This also means that you need to make sure that the user has a valid access token by triggering implicit user authentication.
If the user's access token is available, the operation will succeed. If the resource gateway responds with 401 unauthorized
, the access token is invalid.
private void callImplicitResourceCallToFetchImplicitUserDetails() {
disposables.add(
ImplicitUserService.getInstance(this)
.getImplicitUserDetails()
.subscribe(this::onImplicitUserDetailsFetched, this::onImplicitDetailsFetchFailed)
);
}
private void onImplicitUserDetailsFetched(final ImplicitUserDetails implicitUserDetails) {
implicitUserDetailsTextView.setText(implicitUserDetails.toString());
}
private void onImplicitDetailsFetchFailed(final Throwable throwable) {
implicitUserDetailsTextView.setText(R.string.implicit_user_details_fetch_failed_label);
throwable.printStackTrace();
}
Performing multipart REST request
The retrofit library also supports multipart requests, simply by adding the proper annotation:
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
Performing modified REST requests
To add custom headers to all requests made by your RestAdapter, create a requestInterceptor
instance and set it in the RestAdapter.Builder
when you are constructing your RestAdapter.
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void intercept(RequestFacade request) {
request.addHeader("X-custom-header", "header value");
request.addHeader("Content-type", "application/json");
}
};
RestAdapter restAdapter = new RestAdapter.Builder()
.setRequestInterceptor(requestInterceptor)
.build();
Perform request to any certified baseUrl
By using the retrofit builder, you are able to send requests to any baseUrl thats certificates have been added to.
private static <T> T prepareSecuredRetrofitClient(final Class<T> clazz, final Context context, final OkHttpClient okHttpClient) {
final Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://my.server.com/resources/")
...
.build();
return retrofit.create(clazz);
}