Skip to content

Commit a084d0b

Browse files
authored
feat: Dpop secure credentials manager (#879)
1 parent ddb9db0 commit a084d0b

File tree

3 files changed

+157
-17
lines changed

3 files changed

+157
-17
lines changed

EXAMPLES.md

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,10 +738,10 @@ authentication
738738
> [!NOTE]
739739
> This feature is currently available in [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). Please reach out to Auth0 support to get it enabled for your tenant.
740740
741-
[DPoP](https://www.rfc-editor.org/rfc/rfc9449.html) (Demonstrating Proof of Posession) is an application-level mechanism for sender-constraining OAuth 2.0 access and refresh tokens by proving that the app is in possession of a certain private key. You can enable it by calling the `useDPoP()` method. This ensures that DPoP proofs are generated for requests made through the AuthenticationAPI client.
741+
[DPoP](https://www.rfc-editor.org/rfc/rfc9449.html) (Demonstrating Proof of Possession) is an application-level mechanism for sender-constraining OAuth 2.0 access and refresh tokens by proving that the app is in possession of a certain private key. You can enable it by calling the `useDPoP(context: Context)` method. This ensures that DPoP proofs are generated for requests made through the AuthenticationAPI client.
742742

743743
```kotlin
744-
val client = AuthenticationAPIClient(account).useDPoP()
744+
val client = AuthenticationAPIClient(account).useDPoP(context)
745745
```
746746

747747
[!IMPORTANT]
@@ -785,6 +785,17 @@ DPoP.clearKeyPair()
785785

786786
```
787787

788+
To use DPoP with `SecureCredentialsManager` you need to pass an instance of the `AuthenticationAPIClient` with DPoP enabled to the `SecureCredentialsManager` constructor.
789+
790+
```kotlin
791+
val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN")
792+
val apiClient = AuthenticationAPIClient(auth0).useDPoP(this)
793+
val storage = SharedPreferencesStorage(this)
794+
val manager = SecureCredentialsManager(apiClient, this, auth0, storage)
795+
796+
```
797+
798+
788799
> [!NOTE]
789800
> DPoP is supported only on Android version 6.0 (API level 23) and above. Trying to use DPoP in any older versions will result in an exception.
790801
@@ -1382,6 +1393,28 @@ SecureCredentialsManager manager = new SecureCredentialsManager(this, account, s
13821393
```
13831394
</details>
13841395

1396+
#### Using a Custom AuthenticationAPIClient
1397+
1398+
If you need to configure the `AuthenticationAPIClient` with advanced features (such as DPoP), you can pass your own configured instance to `SecureCredentialsManager`:
1399+
1400+
```kotlin
1401+
val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN")
1402+
val apiClient = AuthenticationAPIClient(auth0).useDPoP(this)
1403+
val storage = SharedPreferencesStorage(this)
1404+
val manager = SecureCredentialsManager(apiClient, this, auth0, storage)
1405+
```
1406+
1407+
<details>
1408+
<summary>Using Java</summary>
1409+
1410+
```java
1411+
Auth0 auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN");
1412+
AuthenticationAPIClient apiClient = new AuthenticationAPIClient(auth0).useDPoP(this);
1413+
Storage storage = new SharedPreferencesStorage(this);
1414+
SecureCredentialsManager manager = new SecureCredentialsManager(apiClient, this, auth0, storage);
1415+
```
1416+
</details>
1417+
13851418
#### Requiring Authentication
13861419

13871420
You can require the user authentication to obtain credentials. This will make the manager prompt the user with the device's configured Lock Screen, which they must pass correctly in order to obtain the credentials. **This feature is only available on devices where the user has setup a secured Lock Screen** (PIN, Pattern, Password or Fingerprint).
@@ -1419,6 +1452,49 @@ SecureCredentialsManager secureCredentialsManager = new SecureCredentialsManager
14191452
```
14201453
</details>
14211454

1455+
You can also combine biometric authentication with a custom `AuthenticationAPIClient`:
1456+
1457+
```kotlin
1458+
val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN")
1459+
val apiClient = AuthenticationAPIClient(auth0).useDPoP(this)
1460+
val localAuthenticationOptions =
1461+
LocalAuthenticationOptions.Builder()
1462+
.setTitle("Authenticate")
1463+
.setDescription("Accessing Credentials")
1464+
.setAuthenticationLevel(AuthenticationLevel.STRONG)
1465+
.setNegativeButtonText("Cancel")
1466+
.setDeviceCredentialFallback(true)
1467+
.setPolicy(BiometricPolicy.Session(300))
1468+
.build()
1469+
val storage = SharedPreferencesStorage(this)
1470+
val manager = SecureCredentialsManager(
1471+
apiClient, this, auth0, storage, fragmentActivity,
1472+
localAuthenticationOptions
1473+
)
1474+
```
1475+
1476+
<details>
1477+
<summary>Using Java</summary>
1478+
1479+
```java
1480+
Auth0 auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN");
1481+
AuthenticationAPIClient apiClient = new AuthenticationAPIClient(auth0).useDPoP(this);
1482+
LocalAuthenticationOptions localAuthenticationOptions =
1483+
new LocalAuthenticationOptions.Builder()
1484+
.setTitle("Authenticate")
1485+
.setDescription("Accessing Credentials")
1486+
.setAuthenticationLevel(AuthenticationLevel.STRONG)
1487+
.setNegativeButtonText("Cancel")
1488+
.setDeviceCredentialFallback(true)
1489+
.setPolicy(new BiometricPolicy.Session(300))
1490+
.build();
1491+
Storage storage = new SharedPreferencesStorage(this);
1492+
SecureCredentialsManager secureCredentialsManager = new SecureCredentialsManager(
1493+
apiClient, this, auth0, storage, fragmentActivity,
1494+
localAuthenticationOptions);
1495+
```
1496+
</details>
1497+
14221498
**Points to be Noted**:
14231499

14241500
On Android API 29 and below, specifying **DEVICE_CREDENTIAL** alone as the authentication level is not supported.

auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
package com.auth0.android.authentication.storage
22

33
import android.text.TextUtils
4-
import android.util.Base64
54
import android.util.Log
65
import androidx.annotation.VisibleForTesting
76
import com.auth0.android.authentication.AuthenticationAPIClient
87
import com.auth0.android.authentication.AuthenticationException
9-
import com.auth0.android.authentication.storage.SecureCredentialsManager.Companion.KEY_CREDENTIALS
108
import com.auth0.android.callback.Callback
119
import com.auth0.android.request.internal.GsonProvider
1210
import com.auth0.android.request.internal.Jwt
1311
import com.auth0.android.result.APICredentials
1412
import com.auth0.android.result.Credentials
15-
import com.auth0.android.result.OptionalCredentials
1613
import com.auth0.android.result.SSOCredentials
1714
import com.auth0.android.result.UserProfile
1815
import com.auth0.android.result.toAPICredentials
1916
import com.google.gson.Gson
2017
import kotlinx.coroutines.suspendCancellableCoroutine
21-
import java.util.*
18+
import java.util.Date
19+
import java.util.Locale
2220
import java.util.concurrent.Executor
2321
import java.util.concurrent.Executors
24-
import kotlin.collections.component1
25-
import kotlin.collections.component2
2622
import kotlin.coroutines.resume
2723
import kotlin.coroutines.resumeWithException
2824

auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,19 @@ import com.auth0.android.authentication.AuthenticationAPIClient
1111
import com.auth0.android.authentication.AuthenticationException
1212
import com.auth0.android.callback.Callback
1313
import com.auth0.android.request.internal.GsonProvider
14-
import com.auth0.android.request.internal.Jwt
1514
import com.auth0.android.result.APICredentials
1615
import com.auth0.android.result.Credentials
1716
import com.auth0.android.result.OptionalCredentials
1817
import com.auth0.android.result.SSOCredentials
1918
import com.auth0.android.result.UserProfile
2019
import com.auth0.android.result.toAPICredentials
2120
import com.google.gson.Gson
22-
import kotlinx.coroutines.CoroutineScope
23-
import kotlinx.coroutines.GlobalScope
24-
import kotlinx.coroutines.launch
2521
import kotlinx.coroutines.suspendCancellableCoroutine
2622
import java.lang.ref.WeakReference
27-
import java.util.*
23+
import java.util.Date
24+
import java.util.Locale
2825
import java.util.concurrent.Executor
2926
import java.util.concurrent.atomic.AtomicLong
30-
import kotlin.collections.component1
31-
import kotlin.collections.component2
3227
import kotlin.coroutines.resume
3328
import kotlin.coroutines.resumeWithException
3429

@@ -65,6 +60,34 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
6560
storage: Storage,
6661
) : this(
6762
AuthenticationAPIClient(auth0),
63+
context,
64+
auth0,
65+
storage
66+
)
67+
68+
/**
69+
* Creates a new SecureCredentialsManager to handle Credentials with a custom AuthenticationAPIClient instance.
70+
* Use this constructor when you need to configure the API client with advanced features like DPoP.
71+
*
72+
* Example usage:
73+
* ```
74+
* val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN")
75+
* val apiClient = AuthenticationAPIClient(auth0).useDPoP(context)
76+
* val manager = SecureCredentialsManager(apiClient, context, auth0, storage)
77+
* ```
78+
*
79+
* @param apiClient a configured AuthenticationAPIClient instance
80+
* @param context a valid context
81+
* @param auth0 the Auth0 account information to use
82+
* @param storage the storage implementation to use
83+
*/
84+
public constructor(
85+
apiClient: AuthenticationAPIClient,
86+
context: Context,
87+
auth0: Auth0,
88+
storage: Storage
89+
) : this(
90+
apiClient,
6891
storage,
6992
CryptoUtil(context, storage, KEY_ALIAS),
7093
JWTDecoder(),
@@ -89,6 +112,50 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
89112
localAuthenticationOptions: LocalAuthenticationOptions
90113
) : this(
91114
AuthenticationAPIClient(auth0),
115+
context,
116+
auth0,
117+
storage,
118+
fragmentActivity,
119+
localAuthenticationOptions
120+
)
121+
122+
123+
/**
124+
* Creates a new SecureCredentialsManager to handle Credentials with biometrics Authentication
125+
* and a custom AuthenticationAPIClient instance.
126+
* Use this constructor when you need to configure the API client with advanced features like DPoP
127+
* along with biometric authentication.
128+
*
129+
* Example usage:
130+
* ```
131+
* val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN")
132+
* val apiClient = AuthenticationAPIClient(auth0).useDPoP(context)
133+
* val manager = SecureCredentialsManager(
134+
* apiClient,
135+
* context,
136+
* auth0,
137+
* storage,
138+
* fragmentActivity,
139+
* localAuthenticationOptions
140+
* )
141+
* ```
142+
*
143+
* @param apiClient a configured AuthenticationAPIClient instance
144+
* @param context a valid context
145+
* @param auth0 the Auth0 account information to use
146+
* @param storage the storage implementation to use
147+
* @param fragmentActivity the FragmentActivity to use for the biometric authentication
148+
* @param localAuthenticationOptions the options of type [LocalAuthenticationOptions] to use for the biometric authentication
149+
*/
150+
public constructor(
151+
apiClient: AuthenticationAPIClient,
152+
context: Context,
153+
auth0: Auth0,
154+
storage: Storage,
155+
fragmentActivity: FragmentActivity,
156+
localAuthenticationOptions: LocalAuthenticationOptions
157+
) : this(
158+
apiClient,
92159
storage,
93160
CryptoUtil(context, storage, KEY_ALIAS),
94161
JWTDecoder(),
@@ -270,7 +337,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
270337
if (credentials == null) {
271338
return null
272339
}
273-
return credentials.user
340+
return credentials.user
274341
}
275342

276343
/**
@@ -1138,7 +1205,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
11381205
internal fun isBiometricSessionValid(): Boolean {
11391206
val lastAuth = lastBiometricAuthTime.get()
11401207
if (lastAuth == NO_SESSION) return false // No session exists
1141-
1208+
11421209
return when (val policy = biometricPolicy) {
11431210
is BiometricPolicy.Session,
11441211
is BiometricPolicy.AppLifecycle -> {
@@ -1149,6 +1216,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
11491216
} * 1000L
11501217
System.currentTimeMillis() - lastAuth < timeoutMillis
11511218
}
1219+
11521220
is BiometricPolicy.Always -> false
11531221
}
11541222
}

0 commit comments

Comments
 (0)