Skip to content

Commit d0ab5d2

Browse files
authored
Adding DPoP feature for flutter (#667)
2 parents 02f5646 + 2ef754e commit d0ab5d2

90 files changed

Lines changed: 2962 additions & 539 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ env:
1616
ruby: '3.3.1'
1717
flutter: '3.x'
1818
ios-simulator: iPhone 16
19-
java: 11
19+
java: 17
2020

2121
jobs:
2222

auth0_flutter/EXAMPLES.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,13 +443,16 @@ final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID',
443443

444444
You can enable an additional level of user authentication before retrieving credentials using the local authentication supported by the device, for example PIN or fingerprint on Android, and Face ID or Touch ID on iOS.
445445

446+
To enable this, pass a `LocalAuthentication` instance when you create your `Auth0` object.
447+
446448
```dart
447449
const localAuthentication =
448450
LocalAuthentication(title: 'Please authenticate to continue');
449451
final auth0 = Auth0('YOUR_AUTH0_DOMAIN', 'YOUR_AUTH0_CLIENT_ID',
450452
localAuthentication: localAuthentication);
451453
final credentials = await auth0.credentialsManager.credentials();
452454
```
455+
> ⚠️ On Android, your app's `MainActivity.kt` file must extend `FlutterFragmentActivity` instead of `FlutterActivity` for biometric prompts to work.
453456
454457
Check the [API documentation](https://pub.dev/documentation/auth0_flutter_platform_interface/latest/auth0_flutter_platform_interface/LocalAuthentication-class.html) to learn more about the available `LocalAuthentication` properties.
455458

@@ -490,10 +493,16 @@ The Credentials Manager will only throw `CredentialsManagerException` exceptions
490493

491494
```dart
492495
try {
493-
final credentials = await auth0.credentialsManager.credentials();
494-
// ...
496+
final credentials = await auth0.credentialsManager.credentials();
497+
// ...
495498
} on CredentialsManagerException catch (e) {
496-
print(e);
499+
if (e.isNoCredentialsFound) {
500+
print("No credentials stored.");
501+
} else if (e.isTokenRenewFailed) {
502+
print("Failed to renew tokens.");
503+
} else {
504+
print(e);
505+
}
497506
}
498507
```
499508

@@ -659,9 +668,23 @@ Fetch the latest user information from the `/userinfo` endpoint.
659668
This method will yield a `UserProfile` instance. Check the [API documentation](https://pub.dev/documentation/auth0_flutter_platform_interface/latest/auth0_flutter_platform_interface/UserProfile-class.html) to learn more about its available properties.
660669

661670
```dart
662-
final userProfile = await auth0.api.userInfo(accessToken: accessToken);
671+
// Basic usage with Bearer token (default)
672+
final userProfile = await auth0.api.userProfile(accessToken: accessToken);
673+
674+
// With explicit token type (useful for DPoP tokens)
675+
final credentials = await auth0.credentialsManager.credentials();
676+
final userProfile = await auth0.api.userProfile(
677+
accessToken: credentials.accessToken,
678+
tokenType: credentials.tokenType, // 'Bearer' or 'DPoP'
679+
);
663680
```
664681

682+
The `tokenType` parameter specifies the type of token being used:
683+
- `'Bearer'` (default): Standard OAuth 2.0 bearer tokens
684+
- `'DPoP'`: DPoP (Demonstrating Proof of Possession) tokens for enhanced security
685+
686+
> 💡 When using DPoP tokens, the SDK automatically handles proof generation. See the [DPoP documentation](DPOP.md) for more information.
687+
665688
### Renew credentials
666689

667690
Use a [refresh token](https://auth0.com/docs/secure/tokens/refresh-tokens) to renew the user's credentials. It's recommended that you read and understand the refresh token process beforehand.

auth0_flutter/MIGRATION_GUIDE.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Migration Guide
2+
3+
## Native SDK Version Updates
4+
5+
This release includes updates to the underlying native Auth0 SDKs to support new features including DPoP (Demonstrating Proof of Possession). These updates are **transparent** to your application code - no code changes are required unless you want to opt into new features like DPoP.
6+
7+
### Updated SDK Versions
8+
9+
| Platform | Previous Version | New Version | Changes |
10+
|----------|-----------------|-------------|---------|
11+
| **Android** | Auth0.Android 2.11.0 | Auth0.Android 3.11.0 | DPoP support, **biometric auth requires FlutterFragmentActivity** |
12+
| **iOS/macOS** | Auth0.swift 2.10.0 | Auth0.swift 2.14.0 | DPoP support, improved APIs |
13+
| **Web** | auth0-spa-js 2.0 | auth0-spa-js 2.9.0 | DPoP support, bug fixes |
14+
15+
### What's New
16+
17+
#### DPoP (Demonstrating Proof of Possession) Support
18+
All platforms now support DPoP, an optional OAuth 2.0 security extension that cryptographically binds access tokens to your client, preventing token theft and replay attacks.
19+
20+
**This is an opt-in feature** - your existing authentication flows will continue to work without any changes.
21+
22+
To enable DPoP:
23+
```dart
24+
// Mobile
25+
final credentials = await auth0.webAuthentication().login(useDPoP: true);
26+
27+
// Web
28+
final auth0Web = Auth0Web('DOMAIN', 'CLIENT_ID', useDPoP: true);
29+
```
30+
31+
For complete DPoP documentation, see the [README](README.md#using-dpop-demonstrating-proof-of-possession).
32+
33+
### Do I Need to Make Changes?
34+
35+
**Most users do not need to make changes.** However, there is one breaking change that affects users of biometric authentication on Android.
36+
37+
#### ⚠️ Breaking Change: Android Biometric Authentication
38+
39+
**If you use biometric authentication on Android**, your `MainActivity.kt` must now extend `FlutterFragmentActivity` instead of `FlutterActivity`.
40+
41+
This requirement comes from Auth0.Android SDK 3.x, which changed its biometric authentication implementation.
42+
43+
**Who is affected:**
44+
- ✅ Users who call `credentialsManager.credentials()` with `localAuthentication` parameter
45+
- ✅ Only on Android platform
46+
- ✅ Only if your `MainActivity.kt` currently extends `FlutterActivity`
47+
48+
**Required change:**
49+
50+
```kotlin
51+
// Before (will cause error)
52+
import io.flutter.embedding.android.FlutterActivity
53+
54+
class MainActivity: FlutterActivity() {
55+
}
56+
57+
// After (required for biometric auth)
58+
import io.flutter.embedding.android.FlutterFragmentActivity
59+
60+
class MainActivity: FlutterFragmentActivity() {
61+
}
62+
```
63+
64+
**If you don't use biometric authentication,** no changes are needed.
65+
66+
#### Optional New Features
67+
68+
You only need to make changes if you want to:
69+
- ✅ Enable DPoP for enhanced security (optional)
70+
- ✅ Use new DPoP API methods: `getDPoPHeaders()` and `clearDPoPKey()` (optional)
71+
72+
### Java Version Requirement (Android)
73+
74+
**Java 8** remains the minimum requirement for Android builds. The SDK continues to use:
75+
- `sourceCompatibility JavaVersion.VERSION_1_8`
76+
- `targetCompatibility JavaVersion.VERSION_1_8`
77+
78+
No changes to your Java setup are needed.
79+
80+
## What's New
81+
82+
This version includes support for **DPoP (Demonstrating Proof of Possession)**, an optional OAuth 2.0 security feature that cryptographically binds access tokens to a specific client. DPoP is completely opt-in and your existing authentication flows will continue to work without any modifications.
83+
84+
For detailed DPoP usage instructions, see the [README DPoP section](README.md#using-dpop-demonstrating-proof-of-possession).

auth0_flutter/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ Re-declare the activity manually using `tools:node="remove"` in the `android/src
202202
203203
> 💡 If your Android app is using [product flavors](https://developer.android.com/studio/build/build-variants#product-flavors), you might need to specify different manifest placeholders for each flavor.
204204
205+
##### Android: Biometric authentication
206+
207+
> ⚠️ On Android, your app's `MainActivity.kt` file must extend `FlutterFragmentActivity` instead of `FlutterActivity` for biometric prompts to work.
208+
205209
##### iOS/macOS: Configure the associated domain
206210

207211
> ⚠️ This step requires a paid Apple Developer account. It is needed to use Universal Links as callback and logout URLs.
@@ -343,6 +347,44 @@ final credentials = await auth0Web.loginWithPopup(popupWindow: popup);
343347
344348
For other comprehensive examples, see the [EXAMPLES.md](EXAMPLES.md) document.
345349

350+
### Using DPoP (Demonstrating Proof of Possession)
351+
352+
Auth0 Flutter SDK supports [DPoP (Demonstrating Proof of Possession)](https://datatracker.ietf.org/doc/html/rfc9449), a security mechanism that cryptographically binds access tokens to your client, preventing token theft and replay attacks.
353+
354+
**Quick Start:**
355+
356+
```dart
357+
// Mobile (Android/iOS)
358+
final credentials = await auth0
359+
.webAuthentication()
360+
.login(useDPoP: true, useHTTPS: true);
361+
362+
// Web
363+
final auth0Web = Auth0Web(
364+
'YOUR_AUTH0_DOMAIN',
365+
'YOUR_AUTH0_CLIENT_ID',
366+
useDPoP: true,
367+
);
368+
```
369+
370+
**Key Benefits:**
371+
- 🔒 Enhanced security through cryptographic token binding
372+
- 🛡️ Protection against token theft and replay attacks
373+
- 🌐 Full cross-platform support (Web, Android, iOS)
374+
375+
**Platform Support:**
376+
377+
| Feature | Web | iOS | Android |
378+
|---------|-----|-----|---------|
379+
| Login with DPoP ||||
380+
| CredentialsManager with DPoP ||||
381+
| Token Refresh with DPoP ||||
382+
| Manual DPoP APIs (`getDPoPHeaders()`, `clearDPoPKey()`) ||||
383+
384+
> **Note:** In most cases, DPoP is managed automatically when `useDPoP: true` is enabled. Manual DPoP APIs are available for advanced use cases where you need direct control over DPoP proof generation.
385+
386+
📖 **For complete DPoP documentation, examples, and troubleshooting, see [DPOP.md](DPOP.md)**
387+
346388
### iOS SSO Alert Box
347389

348390
![Screenshot of the SSO alert box](https://user-images.githubusercontent.com/5055789/198689762-8f3459a7-fdde-4c14-a13b-68933ef675e6.png)

auth0_flutter/android/build.gradle

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ android {
4848
sourceSets {
4949
main.java.srcDirs += 'src/main/kotlin'
5050
test.java.srcDirs += 'src/test/kotlin'
51+
test.resources.srcDirs += 'src/test/resources'
5152
}
5253

5354
defaultConfig {
@@ -71,15 +72,15 @@ android {
7172
}
7273

7374
dependencies {
74-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
75-
//noinspection GradleDynamicVersion
76-
implementation 'com.auth0.android:auth0:2.11.0'
77-
75+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
76+
implementation 'com.auth0.android:auth0:3.11.0'
7877
testImplementation 'junit:junit:4.13.2'
7978
testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0'
80-
testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
79+
testImplementation "org.mockito.kotlin:mockito-kotlin:4.1.0"
80+
testImplementation "org.mockito:mockito-inline:4.11.0"
8181
testImplementation 'com.jayway.awaitility:awaitility:1.7.0'
82-
testImplementation 'org.robolectric:robolectric:4.6.1'
82+
testImplementation 'org.robolectric:robolectric:4.11.1'
8383
testImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
8484
testImplementation 'com.auth0:java-jwt:3.19.1'
85-
}
85+
86+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

auth0_flutter/android/src/main/kotlin/com/auth0/auth0_flutter/Auth0FlutterAuthMethodCallHandler.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
package com.auth0.auth0_flutter
22

3+
import android.content.Context
34
import androidx.annotation.NonNull
45
import com.auth0.android.authentication.AuthenticationAPIClient
5-
import com.auth0.auth0_flutter.request_handlers.api.*
6+
import com.auth0.auth0_flutter.request_handlers.api.ApiRequestHandler
67
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest
78
import io.flutter.plugin.common.MethodCall
89
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
910
import io.flutter.plugin.common.MethodChannel.Result
1011

1112

12-
class Auth0FlutterAuthMethodCallHandler(private val requestHandlers: List<ApiRequestHandler>) : MethodCallHandler {
13+
class Auth0FlutterAuthMethodCallHandler(
14+
private val apiRequestHandlers: List<ApiRequestHandler>
15+
) : MethodCallHandler {
16+
lateinit var context: Context
17+
1318
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
14-
val requestHandler = requestHandlers.find { it.method == call.method }
15-
16-
if (requestHandler != null) {
17-
val request = MethodCallRequest.fromCall(call)
19+
val request = MethodCallRequest.fromCall(call)
20+
21+
val apiHandler = apiRequestHandlers.find { it.method == call.method }
22+
if (apiHandler != null) {
1823
val api = AuthenticationAPIClient(request.account)
24+
25+
val useDPoP = request.data["useDPoP"] as? Boolean ?: false
26+
if (useDPoP) {
27+
api.useDPoP(context)
28+
}
1929

20-
requestHandler.handle(api, request, result)
30+
apiHandler.handle(api, request, result)
2131
} else {
2232
result.notImplemented()
2333
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.auth0.auth0_flutter
2+
3+
import androidx.annotation.NonNull
4+
import com.auth0.auth0_flutter.request_handlers.api.UtilityRequestHandler
5+
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest
6+
import io.flutter.plugin.common.MethodCall
7+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
8+
import io.flutter.plugin.common.MethodChannel.Result
9+
10+
/**
11+
* Handler for DPoP-related method calls.
12+
* DPoP (Demonstration of Proof-of-Possession) operations use static utility methods
13+
* and don't require authentication API client instances.
14+
*/
15+
class Auth0FlutterDPoPMethodCallHandler(
16+
private val dpopRequestHandlers: List<UtilityRequestHandler>
17+
) : MethodCallHandler {
18+
19+
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
20+
val request = MethodCallRequest.fromCall(call)
21+
22+
val dpopHandler = dpopRequestHandlers.find { it.method == call.method }
23+
24+
if (dpopHandler != null) {
25+
dpopHandler.handle(request, result)
26+
} else {
27+
result.notImplemented()
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)