Skip to content

Added webAuth cancel for ios to prevent active transaction errors #573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions auth0_flutter/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [3. How can I disable the iOS _logout_ alert box?](#3-how-can-i-disable-the-ios-logout-alert-box)
- [4. How can I change the message in the iOS alert box?](#4-how-can-i-change-the-message-in-the-ios-alert-box)
- [5. How can I programmatically close the iOS alert box?](#5-how-can-i-programmatically-close-the-ios-alert-box)
- [6. How to resolve the _Failed to start this transaction, as there is an active transaction at the moment_ error?](#6-how-to-resolve-the-failed-to-start-this-transaction-as-there-is-an-active-transaction-at-the-moment-error)
- [🌐 Web](#-web)
- [1. Why is the user logged out when they refresh the page in their SPA?](#1-why-is-the-user-logged-out-when-they-refresh-the-page-in-their-spa)
- [Using Multi-factor Authentication (MFA)](#using-multi-factor-authentication-mfa)
Expand Down Expand Up @@ -116,6 +117,36 @@ This library has no control whatsoever over the alert box. Its contents cannot b

This library has no control whatsoever over the alert box. It cannot be closed programmatically. Unfortunately, that's a limitation of `ASWebAuthenticationSession`.

### 6. How to resolve the _Failed to start this transaction, as there is an active transaction at the moment_ error?

Users might encounter this error when the app moves to the background and then back to the foreground while the login/logout alert box is displayed, for example by locking and unlocking the device. The alert box would get dismissed but when the user tries to log in again, the Web Auth operation fails with the `transactionActiveAlready` error.

This is a known issue with `ASWebAuthenticationSession` and it is not specific to auth0-flutter. We have already filed a bug report with Apple and are awaiting for a response from them.

#### Workarounds

##### Clear the login transaction when handling the `transactionActiveAlready` error

You can invoke `cancelWebAuth()` to manually clear the current login transaction upon encountering this error. Then, you can retry login. For example:

```dart
try {

await webAuth.login(useHTTPS: false);

} catch (e) {

if(e.toString() == 'UNKNOWN: Failed to start this transaction, as there is an active transaction at the moment.') {
webAuth.cancelWebAuth();
//Retry login
}

}
Copy link
Contributor

@Widcket Widcket May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the formatting of the code snippet. A code snippet that is not properly formatted hinders readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

@Widcket Widcket May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if braces are at the same level as those from the catch, and the if block itself is not indented, whereas the contents of the try block are.

Screenshot 2025-05-07 at 15 00 34

```
##### Clear the login transaction when the app moves to the background/foreground

You can invoke `cancelWebAuth()` to manually clear the current login transaction when the app moves to the background or back to the foreground. However, you need to make sure to not cancel valid login attempts –for example, when the user switches briefly to another app while the login page is open.

## 🌐 Web

This library uses the [Auth0 SPA SDK](https://github.com/auth0/auth0-spa-js) on the web platform.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class LoginWebAuthRequestHandlerTest {
val sdf =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)

sdf.timeZone = TimeZone.getTimeZone("UTC")
val formattedDate = sdf.format(defaultCredentials.expiresAt)

verify(result).success(check {
Expand Down Expand Up @@ -331,6 +332,7 @@ class LoginWebAuthRequestHandlerTest {
val sdf =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)

sdf.timeZone = TimeZone.getTimeZone("UTC")
val formattedDate = sdf.format(credentials.expiresAt)

assertThat((captor.firstValue as Map<*, *>)["accessToken"], equalTo(credentials.accessToken))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ class LoginApiRequestHandlerTest {
val sdf =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)

sdf.timeZone = TimeZone.getTimeZone("UTC")
val formattedDate = sdf.format(credentials.expiresAt)

assertThat((captor.firstValue as Map<*, *>)["accessToken"], equalTo(credentials.accessToken))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class LoginWithOtpApiRequestHandlerTest {
val sdf =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)

sdf.timeZone = TimeZone.getTimeZone("UTC")
val formattedDate = sdf.format(credentials.expiresAt)

assertThat((captor.firstValue as Map<*, *>)["accessToken"], equalTo(credentials.accessToken))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ class RenewApiRequestHandlerTest {
val sdf =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)

sdf.timeZone = TimeZone.getTimeZone("UTC")
val formattedDate = sdf.format(credentials.expiresAt)

assertThat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ class GetCredentialsRequestHandlerTest {
val sdf =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)

sdf.timeZone = TimeZone.getTimeZone("UTC")
val formattedDate = sdf.format(credentials.expiresAt)

MatcherAssert.assertThat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class SaveCredentialsRequestHandlerTest {
"scopes" to arrayListOf("a", "b")
)
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
format.timeZone = TimeZone.getTimeZone("UTC")
val date = format.parse(credentialsMap["expiresAt"] as String) as Date
var scope: String? = null
val scopes = (credentialsMap["scopes"] ?: arrayListOf<String>()) as ArrayList<*>
Expand Down Expand Up @@ -204,7 +205,6 @@ class SaveCredentialsRequestHandlerTest {

val captor = argumentCaptor<Credentials>()
verify(mockCredentialsManager).saveCredentials(captor.capture())

assertThat((captor.firstValue).accessToken, equalTo(credentials.accessToken))
assertThat((captor.firstValue).idToken, equalTo(credentials.idToken))
assertThat((captor.firstValue).refreshToken, equalTo(credentials.refreshToken))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation
import Auth0

#if os(iOS)
import Flutter
#else
import FlutterMacOS
#endif


struct WebAuthCancelMethodHandler: MethodHandler {

func handle(with arguments: [String: Any], callback: @escaping FlutterResult) {
WebAuthentication.cancel()
}
}
7 changes: 7 additions & 0 deletions auth0_flutter/darwin/Classes/WebAuth/WebAuthHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ public class WebAuthHandler: NSObject, FlutterPlugin {
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {

if call.method == "webAuth#cancel" {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest to have the call method name as part of string constants

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this as part of the existing Method enum was failing the UTs as all the corresponding handlers expect arguments. Fixing that would require adding some explicit conditions in UTs and code which weren't looking good

let methodHandler = WebAuthCancelMethodHandler()
methodHandler.handle(with: [:], callback: result)
return
}

guard let arguments = call.arguments as? [String: Any] else {
return result(FlutterError(from: .argumentsMissing))
}
Expand Down
14 changes: 14 additions & 0 deletions auth0_flutter/lib/src/mobile/web_authentication.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,23 @@ class WebAuthentication {
await _credentialsManager?.clearCredentials();
}


/// Terminates the ongoing web-based operation and reports back that it was cancelled.
/// You need to call this method within your custom Web Auth provider implementation whenever the operation is
/// cancelled by the user.
/// ## Note:
/// This is an iOS specific API
///
void cancelWebAuth() {
Auth0FlutterWebAuthPlatform.instance.cancelWebAuth();
}

WebAuthRequest<TOptions>
_createWebAuthRequest<TOptions extends RequestOptions>(
final TOptions options) =>
WebAuthRequest<TOptions>(
account: _account, options: options, userAgent: _userAgent);



}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ abstract class Auth0FlutterWebAuthPlatform extends PlatformInterface {
Future<void> logout(final WebAuthRequest<WebAuthLogoutOptions> request) {
throw UnimplementedError('webAuth.logout() has not been implemented');
}

void cancelWebAuth(){
throw UnimplementedError('webAuth.cancelWebAuth() has not been implemented');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const MethodChannel _channel =
MethodChannel('auth0.com/auth0_flutter/web_auth');
const String loginMethod = 'webAuth#login';
const String logoutMethod = 'webAuth#logout';
const String cancelMethod = 'webAuth#cancel';

class MethodChannelAuth0FlutterWebAuth extends Auth0FlutterWebAuthPlatform {
@override
Expand All @@ -33,6 +34,16 @@ class MethodChannelAuth0FlutterWebAuth extends Auth0FlutterWebAuthPlatform {
);
}


@override
void cancelWebAuth() {
try {
_channel.invokeMethod(cancelMethod);
} on PlatformException catch (e) {
throw WebAuthenticationException.fromPlatformException(e);
}
}

Future<Map<String, dynamic>> invokeRequest<TOptions extends RequestOptions>({
required final String method,
required final WebAuthRequest<TOptions> request,
Expand Down
Loading