Commit 03624928 authored by Claudio Marforio's avatar Claudio Marforio
Browse files

Version 1.0.0

- Maven integration (and update to docs)
- Proguard rules sample
- Update sample app
- Update version of futuraekit.aar to 1.0.0
parent 85fa5584
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion '28.0.3' buildToolsVersion '29.0.2'
defaultConfig { defaultConfig {
applicationId "com.futurae.futuraedemo" applicationId "com.futurae.futuraedemo"
...@@ -14,17 +14,28 @@ android { ...@@ -14,17 +14,28 @@ android {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { debug {
minifyEnabled false minifyEnabled true
proguardFile 'proguard/futurae.pro'
proguardFile getDefaultProguardFile('proguard-android.txt')
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
debug {
} }
}
repositories {
maven {
url "https://artifactory.futurae.com/artifactory/futurae-mobile"
credentials {
username = "anonymous"
password = ""
} }
productFlavors {
} }
} }
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:appcompat-v7:28.0.0'
...@@ -37,12 +48,7 @@ dependencies { ...@@ -37,12 +48,7 @@ dependencies {
implementation 'com.google.firebase:firebase-core:16.0.7' implementation 'com.google.firebase:firebase-core:16.0.7'
implementation 'com.google.firebase:firebase-messaging:17.4.0' implementation 'com.google.firebase:firebase-messaging:17.4.0'
implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation('com.futurae.sdk:futuraekit:1.0.0')
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.moshi:moshi-adapters:1.6.0'
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
implementation(name:'futuraekit', ext:'aar')
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
-keep public enum com.futurae.sdk.model.Account$** {
**[] $VALUES;
public *;
}
-keep class com.futurae.sdk.DeviceInfo {
*;
}
\ No newline at end of file
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera" />
......
...@@ -2,6 +2,7 @@ package com.futurae.futuraedemo; ...@@ -2,6 +2,7 @@ package com.futurae.futuraedemo;
import android.app.Application; import android.app.Application;
import com.futurae.sdk.FuturaeClient; import com.futurae.sdk.FuturaeClient;
import com.futurae.sdk.Kit;
public class AppMain extends Application { public class AppMain extends Application {
...@@ -11,6 +12,6 @@ public class AppMain extends Application { ...@@ -11,6 +12,6 @@ public class AppMain extends Application {
super.onCreate(); super.onCreate();
FuturaeClient.launch(this, null); FuturaeClient.launch(this, (Kit)null);
} }
} }
...@@ -21,9 +21,12 @@ import android.util.Log; ...@@ -21,9 +21,12 @@ import android.util.Log;
import com.futurae.futuraedemo.R; import com.futurae.futuraedemo.R;
import com.futurae.sdk.FuturaeCallback; import com.futurae.sdk.FuturaeCallback;
import com.futurae.sdk.FuturaeClient; import com.futurae.sdk.FuturaeClient;
import com.futurae.sdk.FuturaeResultCallback;
import com.futurae.sdk.approve.ApproveSession; import com.futurae.sdk.approve.ApproveSession;
import com.futurae.sdk.model.Account; import com.futurae.sdk.model.Account;
import com.futurae.sdk.model.ApproveInfo;
import com.futurae.sdk.model.CurrentTotp; import com.futurae.sdk.model.CurrentTotp;
import com.futurae.sdk.model.SessionInfo;
import com.futurae.sdk.utils.NotificationUtils; import com.futurae.sdk.utils.NotificationUtils;
import com.google.android.gms.vision.barcode.Barcode; import com.google.android.gms.vision.barcode.Barcode;
...@@ -69,19 +72,14 @@ public class MainActivity extends AppCompatActivity { ...@@ -69,19 +72,14 @@ public class MainActivity extends AppCompatActivity {
// TODO: Handle URI call // TODO: Handle URI call
final String uriCall = getIntent().getDataString(); final String uriCall = getIntent().getDataString();
if (!TextUtils.isEmpty(uriCall)) { if (!TextUtils.isEmpty(uriCall)) {
// Enrollment URI
if (uriCall.contains("enroll")) {
FuturaeClient.sharedClient().handleUri(uriCall, new FuturaeCallback() { FuturaeClient.sharedClient().handleUri(uriCall, new FuturaeCallback() {
@Override @Override
public void success() { public void success() {
Log.i(TAG, "success: URI handled"); Log.i(TAG, "success: URI handled");
// TODO: Handle enrollment and MobileAuth
if (uriCall.contains("enroll")) {
// URI Enrollment
showAlert("Success", "Successfully enrolled"); showAlert("Success", "Successfully enrolled");
} else {
// MobileAuth
finish();
}
} }
@Override @Override
...@@ -90,6 +88,30 @@ public class MainActivity extends AppCompatActivity { ...@@ -90,6 +88,30 @@ public class MainActivity extends AppCompatActivity {
showAlert("Error", "Could not handle URI call"); showAlert("Error", "Could not handle URI call");
} }
}); });
return;
}
// Auth URI
if (uriCall.contains("auth")) {
String userId = FuturaeClient.getUserIdFromUri(uriCall);
String sessionToken = FuturaeClient.getSessionTokenFromUri(uriCall);
FuturaeClient.sharedClient().sessionInfoByToken(userId, sessionToken,
new FuturaeResultCallback<SessionInfo>() {
@Override
public void success(SessionInfo sessionInfo) {
showApproveAlertDialog(new ApproveSession(sessionInfo), true);
}
@Override
public void failure(Throwable t) {
Log.e(TAG, "QR Code authentication failed: " + t.getLocalizedMessage());
}
});
return;
}
Log.e(TAG, "failure: failed to handle URI: " + uriCall);
showAlert("Error", "Could not handle URI call");
} }
} }
...@@ -145,7 +167,7 @@ public class MainActivity extends AppCompatActivity { ...@@ -145,7 +167,7 @@ public class MainActivity extends AppCompatActivity {
final Account account = accounts.get(0); final Account account = accounts.get(0);
CurrentTotp totp = FuturaeClient.sharedClient().nextTotp(account.getUserId()); CurrentTotp totp = FuturaeClient.sharedClient().nextTotp(account.getUserId());
showAlert("TOTP", "Code: " + totp.passcode + "\nRemaining seconds: " + totp.remainingSecs); showAlert("TOTP", "Code: " + totp.getPasscode() + "\nRemaining seconds: " + totp.getRemainingSecs());
} }
// QRCode callbacks // QRCode callbacks
...@@ -171,32 +193,39 @@ public class MainActivity extends AppCompatActivity { ...@@ -171,32 +193,39 @@ public class MainActivity extends AppCompatActivity {
} }
private void onAuthQRCodeScanned(Intent data) { private void onAuthQRCodeScanned(Intent data) {
// TODO: Handle QRCode auth response
Barcode qrcode = data.getParcelableExtra(FTRQRCodeActivity.PARAM_BARCODE); Barcode qrcode = data.getParcelableExtra(FTRQRCodeActivity.PARAM_BARCODE);
Log.i(TAG, "Scanned authentication data from the QR code; will reply to server"); String userId = FuturaeClient.getUserIdFromQrcode(qrcode.rawValue);
FuturaeClient.sharedClient().approveAuth(qrcode.rawValue, String sessionToken = FuturaeClient.getSessionTokenFromQrcode(qrcode.rawValue);
new FuturaeCallback() {
FuturaeClient.sharedClient().sessionInfoByToken(userId, sessionToken,
new FuturaeResultCallback<SessionInfo>() {
@Override @Override
public void success() { public void success(SessionInfo sessionInfo) {
Log.i(TAG, "QR Code authentication succeeded"); showApproveAlertDialog(new ApproveSession(sessionInfo), false);
} }
@Override @Override
public void failure(Throwable throwable) { public void failure(Throwable t) {
Log.e(TAG, "QR Code authentication failed: " Log.e(TAG, "QR Code authentication failed: " + t.getLocalizedMessage());
+ throwable.getLocalizedMessage());
} }
}); });
} }
// Approve dialog // Approve dialog
void showApproveAlertDialog(final ApproveSession session) { void showApproveAlertDialog(final ApproveSession session, final boolean isFromUri) {
// TODO: For demo purposes we simply show an alert instead of an approve screen // TODO: For demo purposes we simply show an alert instead of an approve screen
StringBuffer sb = new StringBuffer();
if (session.getInfo() != null) {
sb.append("\n");
for (ApproveInfo info : session.getInfo()) {
sb.append(info.getKey()).append(": ").append(info.getValue()).append("\n");
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Approve"); builder.setTitle("Approve");
builder.setMessage("Would you like to approve the request?"); builder.setMessage("Would you like to approve the request?" + sb.toString());
builder.setPositiveButton("Approve", new DialogInterface.OnClickListener() { builder.setPositiveButton("Approve", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
FuturaeClient.sharedClient().approveAuth(session.getUserId(), FuturaeClient.sharedClient().approveAuth(session.getUserId(),
...@@ -204,13 +233,16 @@ public class MainActivity extends AppCompatActivity { ...@@ -204,13 +233,16 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
public void success() { public void success() {
Log.i(TAG, "Approve session allowed"); Log.i(TAG, "Approve session allowed");
if (isFromUri) {
finish();
}
} }
@Override @Override
public void failure(Throwable t) { public void failure(Throwable t) {
Log.e(TAG, "Failed to approve session: " + t.getLocalizedMessage()); Log.e(TAG, "Failed to approve session: " + t.getLocalizedMessage());
} }
}); }, session.getInfo());
} }
}); });
builder.setNegativeButton("Deny", new DialogInterface.OnClickListener() { builder.setNegativeButton("Deny", new DialogInterface.OnClickListener() {
...@@ -220,13 +252,16 @@ public class MainActivity extends AppCompatActivity { ...@@ -220,13 +252,16 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
public void success() { public void success() {
Log.i(TAG, "Approve session rejected"); Log.i(TAG, "Approve session rejected");
if (isFromUri) {
finish();
}
} }
@Override @Override
public void failure(Throwable t) { public void failure(Throwable t) {
Log.e(TAG, "Failed to approve session: " + t.getLocalizedMessage()); Log.e(TAG, "Failed to approve session: " + t.getLocalizedMessage());
} }
}); }, session.getInfo());
} }
}); });
...@@ -303,10 +338,14 @@ public class MainActivity extends AppCompatActivity { ...@@ -303,10 +338,14 @@ public class MainActivity extends AppCompatActivity {
final ApproveSession session = intent.getParcelableExtra( final ApproveSession session = intent.getParcelableExtra(
NotificationUtils.PARAM_APPROVE_SESSION); NotificationUtils.PARAM_APPROVE_SESSION);
if (approveDialog != null && approveDialog.isShowing()) {
approveDialog.dismiss();
}
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
showApproveAlertDialog(session); showApproveAlertDialog(session, false);
} }
}); });
break; break;
......
...@@ -5,7 +5,7 @@ buildscript { ...@@ -5,7 +5,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.3.2' classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.google.gms:google-services:4.2.0' classpath 'com.google.gms:google-services:4.2.0'
} }
} }
...@@ -14,9 +14,6 @@ allprojects { ...@@ -14,9 +14,6 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
flatDir {
dirs 'src/main/libs'
}
} }
} }
......
#Fri Mar 15 11:07:02 CET 2019 #Wed Jul 22 17:41:42 CEST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
...@@ -5,11 +5,12 @@ This is the Android SDK of Futurae. You can read more about Futurae™ at [futur ...@@ -5,11 +5,12 @@ This is the Android SDK of Futurae. You can read more about Futurae™ at [futur
## Table of contents ## Table of contents
* [Basic integration](#basic-integration) * [Basic integration](#basic-integration)
* [Get FuturaeKit SDK for Android](#get-futuraekit-sdk-for-android) * [Get FuturaeKit SDK for Android via Maven](#get-futuraekit-sdk-for-android-via-maven)
* [Add SDK to Project](#add-sdk-to-project) * [Get FuturaeKit SDK for Android Manually](#get-futuraekit-sdk-for-android-manually)
* [Add permissions](#add-permissions) * [Add permissions](#add-permissions)
* [Basic setup](#basic-setup) * [Basic setup](#basic-setup)
* [Build your app](#build-your-app) * [Build your app](#build-your-app)
* [R8 / Proguard](#r8-proguard)
* [Features](#features) * [Features](#features)
* [Callbacks](#callbacks) * [Callbacks](#callbacks)
* [URI Schemes](#uri-schemes) * [URI Schemes](#uri-schemes)
...@@ -32,21 +33,43 @@ This is the Android SDK of Futurae. You can read more about Futurae™ at [futur ...@@ -32,21 +33,43 @@ This is the Android SDK of Futurae. You can read more about Futurae™ at [futur
## <a id="basic-integration" />Basic integration ## <a id="basic-integration" />Basic integration
We will describe the steps to integrate the FuturaeKit SDK into your Android project. We are going to assume that you are using Android Studio for your development. We will describe the steps to integrate the FuturaeKit SDK into your Android project. We are going to assume that you are using Android Studio for your development.
### <a id="get-futuraekit-sdk-for-android" />Get FuturaeKit SDK for Android ### <a id="get-futuraekit-sdk-for-android-via-maven" />Get FuturaeKit SDK for Android via Maven
You can download the latest SDK from the [releases](https://git.futurae.com/futurae-public/futurae-android-sdk/tags), or clone this repository directly. The *preferred* way to get the FuturaeKit SDK fro Android is through maven and the Futurae private repository.
This repository also contains a simple demo app to show how the SDK can be integrated.
To do so add the following lines to your `build.gradle` file:
```
repositories {
maven {
url "https://artifactory.futurae.com/artifactory/futurae-mobile"
credentials {
username = "anonymous"
password = ""
}
}
}
dependencies {
implementation('com.futurae.sdk:futuraekit:1.0.0')
}
```
Of course, make sure to specify the correct version number.
### <a id="get-futuraekit-sdk-for-android-via-maven" />Get FuturaeKit SDK for Android Manually
Alternatively, *although discouraged*, you can download the latest SDK from the [releases](https://git.futurae.com/futurae-public/futurae-android-sdk/tags), or clone this repository directly.
This repository also contains a simple demo app to show how the SDK can be integrated.
### <a id="add-sdk-to-project" />Add SDK to Project
To integrate the FuturaeKit SDK into your project, copy `futuraekit.aar` into the `src/main/libs` folder of your app. To integrate the FuturaeKit SDK into your project, copy `futuraekit.aar` into the `src/main/libs` folder of your app.
Then, in your modules `build.gradle` (the one under "app"), add the following dependencies: Then, in your modules `build.gradle` (the one under "app"), add the following dependencies:
``` ```
compile 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-moshi:2.3.0' implementation 'com.squareup.retrofit2:converter-moshi:2.3.0'
compile 'com.squareup.moshi:moshi-adapters:1.4.0' implementation 'com.squareup.moshi:moshi-adapters:1.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.8.0' implementation 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
compile(name:'futuraekit', ext:'aar') implementation 'com.github.nisrulz:easydeviceinfo-base:2.4.1'
implementation files('src/main/libs/futuraekit.aar')
``` ```
![][gradle-app] ![][gradle-app]
...@@ -124,6 +147,12 @@ Secondly, in your `res/values` folder make sure to create a file `futurae.xml` w ...@@ -124,6 +147,12 @@ Secondly, in your `res/values` folder make sure to create a file `futurae.xml` w
Build and run your app. If the build succeeds, you should carefully read the SDK logs in the console. Build and run your app. If the build succeeds, you should carefully read the SDK logs in the console.
### <a id="r8-proguard" />R8 / ProGuard
If you are using R8 or ProGuard add the options from [build.gradle][build_gradle].
## <a id="features" />Features ## <a id="features" />Features
### <a id="callbacks" />Callbacks ### <a id="callbacks" />Callbacks
The SDK methods that perform API calls use callbacks as the feedback mechanism. These calls expect an object of the `FuturaeCallback` interface as an argument: The SDK methods that perform API calls use callbacks as the feedback mechanism. These calls expect an object of the `FuturaeCallback` interface as an argument:
...@@ -152,6 +181,8 @@ FuturaeClient.sharedClient().handleUri(uriString, new FuturaeCallback() { ...@@ -152,6 +181,8 @@ FuturaeClient.sharedClient().handleUri(uriString, new FuturaeCallback() {
}); });
``` ```
**Note**: In case you attempt an authentication with a URI scheme call, and this authentication includes extra information to be displayed to the user, you must retrieve this information from the server and include it in the authentication response; see section [Session Information](#session-information) for details on how to do that.
### <a id="push-notifications" />Push Notifications ### <a id="push-notifications" />Push Notifications
Your app must be set up to receive Firebase Cloud Messaging (FCM) push notifications from our server. You can choose to receive and handle these notifications yourself, or alternatively you can use the existing infrastructure provided in the SDK. You can find more information on how to setup FCM push notifications for your app in the [Firebase Cloud Messaging Developer Guide](https://firebase.google.com/docs/cloud-messaging/). Your app must be set up to receive Firebase Cloud Messaging (FCM) push notifications from our server. You can choose to receive and handle these notifications yourself, or alternatively you can use the existing infrastructure provided in the SDK. You can find more information on how to setup FCM push notifications for your app in the [Firebase Cloud Messaging Developer Guide](https://firebase.google.com/docs/cloud-messaging/).
...@@ -348,6 +379,8 @@ FuturaeClient.sharedClient().approveAuth(qrCodeString, new FuturaeCallback() { ...@@ -348,6 +379,8 @@ FuturaeClient.sharedClient().approveAuth(qrCodeString, new FuturaeCallback() {
}); });
``` ```
**Note**: In case you attempt an authentication with a QR Code, and this authentication includes extra information to be displayed to the user, you must retrieve this information from the server and include it in the authentication response; see section [Session Information](#session-information) for details on how to do that.
#### <a id="approve-auth" />Push Notification Factor #### <a id="approve-auth" />Push Notification Factor
When a Push Notification Factor session is initiated on the server side, the server will send a push notification to the app, where the user should approve or reject the authentication session. The notification is received and handled by the SDK, which in turn will send a local broadcast (see `INTENT_APPROVE_AUTH_MESSAGE` [above](#local-intents)), so that the app can perform any required actions. For example, the app might want to display a prompt to the user so that they can approve or reject the session. When a Push Notification Factor session is initiated on the server side, the server will send a push notification to the app, where the user should approve or reject the authentication session. The notification is received and handled by the SDK, which in turn will send a local broadcast (see `INTENT_APPROVE_AUTH_MESSAGE` [above](#local-intents)), so that the app can perform any required actions. For example, the app might want to display a prompt to the user so that they can approve or reject the session.
...@@ -360,6 +393,8 @@ String sessionId = authSession.getSessionId(); ...@@ -360,6 +393,8 @@ String sessionId = authSession.getSessionId();
Once the outcome of the authentication has been decided by the app, it should be sent to the server for the authentication session to complete. Once the outcome of the authentication has been decided by the app, it should be sent to the server for the authentication session to complete.
**Note**: In case you attempt an authentication via push notification, and this authentication includes extra information to be displayed to the user, you must retrieve this information from the server and include it in the authentication response; see section [Session Information](#session-information) for details on how to do that.
##### <a id="approve-reply" />Approve Authentication ##### <a id="approve-reply" />Approve Authentication
To approve the authentication session, use the following method: To approve the authentication session, use the following method:
```java ```java
...@@ -397,8 +432,8 @@ FuturaeClient.sharedClient().rejectAuth(userId, sessionId, reportFraud, new Futu ...@@ -397,8 +432,8 @@ FuturaeClient.sharedClient().rejectAuth(userId, sessionId, reportFraud, new Futu
The TOTP Factor can be used for offline authentication, as there is no requirement for an internet connection in the app. To get the current TOTP generated by the SDK for a specific user account, call the following method: The TOTP Factor can be used for offline authentication, as there is no requirement for an internet connection in the app. To get the current TOTP generated by the SDK for a specific user account, call the following method:
```java ```java
CurrentTotp totp = FuturaeClient.sharedClient().nextTotp(userId); CurrentTotp totp = FuturaeClient.sharedClient().nextTotp(userId);
String passcode = totp.passcode; // The TOTP that the user should use to authenticate String passcode = totp.getPasscode(); // The TOTP that the user should use to authenticate
int remainingSeconds = totp.remainingSecs; // The remaining seconds of validity of this TOTP int remainingSeconds = totp.getRemainingSecs(); // The remaining seconds of validity of this TOTP
``` ```
As seen in this example, the `nextTotp()` method returns an object that contains the TOTP itself, but also the remaining seconds that this TOTP will be still valid for. After this time, a new TOTP must be obtained by the SDK. As seen in this example, the `nextTotp()` method returns an object that contains the TOTP itself, but also the remaining seconds that this TOTP will be still valid for. After this time, a new TOTP must be obtained by the SDK.
...@@ -436,6 +471,39 @@ FuturaeClient.sharedClient().sessionInfoByToken(userId, sessionId, new FuturaeRe ...@@ -436,6 +471,39 @@ FuturaeClient.sharedClient().sessionInfoByToken(userId, sessionId, new FuturaeRe
If there is extra information to be displayed to the user, for example when confirming a transaction, this will be indicated with a list of key-value pairs in the `extra_info` part of the response. If there is extra information to be displayed to the user, for example when confirming a transaction, this will be indicated with a list of key-value pairs in the `extra_info` part of the response.
In order to query the server for the session information, you need the user ID and the session ID or token. You can use the following helper methods to obtain these from either a URI or a QR Code:
```java
public class FuturaeClient {
public static String getUserIdFromQrcode(String qrCode);
public static String getSessionTokenFromQrcode(String qrCode);
public static String getUserIdFromUri(String uri);
public static String getSessionTokenFromUri(String uri);
}
```
##### <a id="auth-extra-info" />Authentication with extra information
In case it exists, you can retrieve the extra information that the user needs to review while authenticating by calling the following method on the `SessionInfo` object:
```java
public ApproveInfo[] getApproveInfo();
```
Each `ApproveInfo` object is a key-value pair that describes a single piece of information; call the following methods to retrieve the key and value of the object respectively.
```java
public String getKey();
public String getValue();
```
**IMPORTANT**: In case the authentication contains such extra information, it is mandatory that the information is fetched from the server, displayed to the user, and included in the authentication response in order for it to be included in the response signature; otherwise the server will reject the authentication response. Use the following methods to send an authentication response including the authentication information:
```java
class FuturaeClient {
public void approveAuth(String qrCode, final FuturaeCallback callback, ApproveInfo[] extraInfo);
public void approveAuth(String userId, String sessionId, final FuturaeCallback callback, ApproveInfo[] extraInfo);
public void rejectAuth(String userId, String sessionId, boolean reportFraud, final FuturaeCallback callback, ApproveInfo[] extraInfo);
}