Using Box Java SDK in Android app with OAuth 2.0 — Part 1

Mateusz Woda
Box Developer Blog
Published in
7 min readApr 4, 2023
Using Box Java SDK in Android app with OAuth 2.0

Box offers various SDKs for different languages, including Java, C#, Node.js and many others. Box also has a dedicated SDK for Android, but it is not currently under active development and will be discontinued soon. This can be a cumbersome for those who want to build mobile apps quickly. Fortunately, we have an alternative — we can use the existing Box Java SDK in our Android applications.

In this blog post, I will guide you on how to create an Android application that authenticates with the Box API using OAuth 2.0 and displays the contents of the root folder.

Prerequisites: Box developer account, Java (minimum required version for Box Java SDK is Java 8), Android Studio (not mandatory, but will make things easier)

Set up Box Custom App

First, you need a Box developer account with access to the developer console. Create a Custom App with OAuth 2.0 as the authentication method.

Make sure that Read all files and folders stored in Application Scopes is enabled.

OAuth also requires a Redirect URI, which will be used by Box to redirect to the application after the user logs in. You can set it to any valid uri. Let’s set it to oauth://boxinandroid.

While you are in developer console let’s also take a note of Client ID and Client Secret. They will be useful later.

Set up project in Android Studio

Open Android Studio and create a new project. Select a project with Empty Activity. Let’s name it BoxJavaSDKInAndroid and click Finish.

Before you start writing code, add dependencies for box-java-sdk and androidx.browser to the project’s build.gradle. CustomTabs feature of androidx.browser will be used to display browser tab in the mobile device. build.gradle file should contain both of those lines in dependencies block.

implementation 'com.box:box-java-sdk:4.0.1'
implementation 'androidx.browser:browser:1.5.0'

This app will also need a Client ID and Client Secret to talk to BoxAPI. Let’s create a secrets.xml file in the app/res/values directory to store these secrets.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="client_id">YOUR_CLIENT_ID</string>
<string name="client_secret">YOUR_SECRET</string>
</resources>

⚠️ Note: Remember that storing secrets in this way is not safe. Your users can easily reverse engineer or decompile the binary and access them. If you want to prevent this, look for other ways to store secrets on Android devices.

Authenticate with Box API using OAuth 2.0

First, let’s add button to the app so that when the user clicks on it, they will be redirected to the Box page where they can authenticate using their account. Open activity_main.xml and replace the existing Hello World TextView with the xml code for a SignIn button. activity_main.xml should look like this.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<Button
android:id="@+id/sign_in_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sign in with Box"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now let’s write code for OAuth. Open MainActivity and add a function called launchBoxTab that creates all the required parameters for OAuth and open a tab in browser using CustomTabs.

You can use the BoxAPIConnection.getAuthorizationURL method from box-java-sdk to create the authorizationURL instead of doing it yourself. How convenient! This method requires a few parameters.

  1. Client ID from the Box App.
  2. Redirect URI added before in the Box App.
  3. State that can be used to prevent attacks like CSRF.
  4. List of scopes will be needed to receive a token with the appropriate permissions. You can read more about the scopes in the Box documentation. To read files and folders from Box root_readonly scope is required.

launchBoxTab function should look like this.

private void launchBoxTab() {
List<String> scopes = Collections.singletonList("root_readonly");
URI redirectUri = URI.create("oauth://boxinandroid");
state = UUID.randomUUID().toString();
URL authURL = BoxAPIConnection.getAuthorizationURL(getString(R.string.client_id),
redirectUri, state, scopes);
new CustomTabsIntent.Builder().build().launchUrl(this, Uri.parse(authURL.toString()));
}

The generated state is stored in a variable belonging to MainActivity so that it can be used later for validation.

public class MainActivity extends AppCompatActivity {

private String state;

///
/// rest of the activity code
///
}

Add launchBoxTab to the onClickListener of the SignIn button. Place it in the onCreate function of MainActivity.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button signInButton = (Button) findViewById(R.id.sign_in_button);
signInButton.setOnClickListener(view -> launchBoxTab());
}

Activity lifecycle is a complex topic in Android. Activities can be destroyed by OS by various reasons like screen rotation, lack of system resources etc. Along with this, the dynamic state of activity (like variable values) is also lost. To preserve the OAuth state parameter when the activity is recreated, let’s use onSaveInstanceState and onRestoreInstanceState respectively. Bundle can be used to store state variable.

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("State", state);
super.onSaveInstanceState(savedInstanceState);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
state = savedInstanceState.getString("State");
}

After receiving the code from the Box API, the application will run another Activity that lists the contents of the root folder. You can use Intent to handle callback from Box API.

First add a onNewIntent function to the MainActivity

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null) {
Uri callback = intent.getData();
String callbackState = callback.getQueryParameter("state");

if (!Objects.equals(callbackState, state)) {
Toast.makeText(MainActivity.this, "State validation failed!", Toast.LENGTH_LONG).show();
return;
}

String code = callback.getQueryParameter("code");
Intent itemsIntent = new Intent(MainActivity.this, ItemsActivity.class);
itemsIntent.putExtra("code", code);
MainActivity.this.startActivity(itemsIntent);
}
}

The generated state is compared with the state returned from the Box API. If they are not equal, an error is displayed. If validation succeeds, a new activity named ItemsActivity is started and the received code will be sent to it. This code will be passed to the box-java-sdk later. To avoid compilation errors, let’s add a new Activity named ItemsActivity and a corresponding layout file. For now this Activity will just print the received code on the screen.

ItemsActivity code

package com.example.boxjavasdkinandroid;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class ItemsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_items);

Bundle extras = getIntent().getExtras();
if (extras != null) {
String code = extras.getString("code");
if (!code.isEmpty()) {
TextView codeTextView = findViewById(R.id.code_text_view);
codeTextView.setText(code);
}
}
}
}

activity.items.xml (Items activity layout) code

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemsActivity">

<TextView
android:id="@+id/code_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Here is the finished MainActivity code

package com.example.boxjavasdkinandroid;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.browser.customtabs.CustomTabsIntent;

import com.box.sdk.BoxAPIConnection;

import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

private String state;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button signInButton = (Button) findViewById(R.id.sign_in_button);
signInButton.setOnClickListener(view -> launchBoxTab());
}

private void launchBoxTab() {
List<String> scopes = Collections.singletonList("root_readonly");
URI redirectUri = URI.create("oauth://boxinandroid");
state = UUID.randomUUID().toString();
URL authURL = BoxAPIConnection.getAuthorizationURL(getString(R.string.client_id),
redirectUri, state, scopes);
new CustomTabsIntent.Builder().build().launchUrl(this, Uri.parse(authURL.toString()));
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("State", state);
super.onSaveInstanceState(savedInstanceState);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
state = savedInstanceState.getString("State");
}

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null) {
Uri callback = intent.getData();
String callbackState = callback.getQueryParameter("state");

if (!Objects.equals(callbackState, state)) {
Toast.makeText(MainActivity.this, "State validation failed!", Toast.LENGTH_LONG).show();
return;
}

String code = callback.getQueryParameter("code");
Intent itemsIntent = new Intent(MainActivity.this, ItemsActivity.class);
itemsIntent.putExtra("code", code);
MainActivity.this.startActivity(itemsIntent);
}
}
}

One more thing that is required before launching the application is to modify AndroidManifest.xml. You need to do a few things here

  1. Add INTERNET permission to make http/s calls.
  2. Add a reference to ItemsActivity.
  3. Add an Intent filter that will be used to properly handle callback from Box API.
  4. Modify the existing MainActivity so that it works with the new Intent.

After all the changes, AndroidManifest.xml should look like this

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.BoxJavaSDKInAndroid"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<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:host="boxinandroid"
android:scheme="oauth" />
</intent-filter>
</activity>
<activity
android:name=".ItemsActivity"
android:exported="false" />
</application>

</manifest>

Note that the same Redirect URI that was set in the Box Custom App is also used here in the Intent filter in the data section.

Run the application

Start the application. You should see a screen with a button to sign in.

After clicking the button, you should be redirected to the Box page, which will ask you to enter four credentials.

After entering the email, password and granting the app permissions, you should be redirected back to the ItemsActivity and the code received from Box should be displayed on the screen.

Congratulations! You are now authenticated in the Box API using OAuth 2.0. In the next blog post, we will try to retrieve some data from Box using the Box Java SDK.

Source code: https://github.com/box-community/android-oauth-with-sdk/tree/main/part1

Want to learn more? Visit our developer documentation site. Feel free to reach out to us on the developer forum for support or via Box Pulse to make suggestions on how to improve the Box developer experience.

Follow our Box Developers profile on Twitter to stay up-to-date.

Cheers!

--

--