Skip to content
Draft
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
38 changes: 35 additions & 3 deletions packages/supabase_flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ Future<AuthResponse> _googleSignIn() async {

### <a id="oauth-login"></a>OAuth login

The `signInWithIdToken()` method supports providers like Apple, Google, Facebook, Kakao, and Keycloak. For other providers, you need to use the `signInWithOAuth()` method to perform OAuth login. This will open the web browser to perform the OAuth login.
The `signInWithIdToken()` method supports providers like Apple, Google, Facebook, Kakao, and Keycloak. For other providers, you need to use the `signInWithOAuth()` method to perform OAuth login. On native platforms this opens a system web authentication session (`ASWebAuthenticationSession` on iOS and macOS, Custom Tabs on Android) that closes itself once the login completes. On web the current tab is redirected to the provider.

Use the `redirectTo` parameter to redirect the user to a deep link to bring the user back to the app. Learn more about setting up deep links in [Deep link config](#deep-link-config).
Use the `redirectTo` parameter to send the user back to the app after login. Its scheme is also the callback scheme the web authentication session listens for, so on native platforms you need to register it. See [OAuth native config](#oauth-native-config).

```dart
// Perform web based OAuth login
Expand All @@ -206,6 +206,37 @@ supabase.auth.onAuthStateChange.listen((data) {
});
```

#### <a id="oauth-native-config"></a>OAuth native config

On native platforms `signInWithOAuth()`, `signInWithSSO()` and `linkIdentity()` run inside a system web authentication session that captures the redirect back to your app. The session listens for the scheme of your `redirectTo` URL, so that scheme has to be registered with the platform.

**Android**

Register the callback activity in `android/app/src/main/AndroidManifest.xml`, inside the `<application>` tag, using the scheme of your `redirectTo` (here `io.supabase.flutter`):

```xml
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true">
<intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="io.supabase.flutter" />
</intent-filter>
</activity>
```

**iOS and macOS**

No configuration is required for custom URL schemes. If you use an `https` `redirectTo` (universal link), it is passed through automatically; on iOS 17.4+ and macOS 14.4+ the host and path are taken from `redirectTo` to satisfy the universal link requirements.

**Web**

No configuration is required. The current tab is redirected to the provider and the session is restored when the browser returns to your app.

If you only need OAuth, SSO and identity linking, this replaces the app links deep link setup below. You still need to set up deep links for magic links, email confirmation and password recovery, which arrive as real deep links from outside the app.

### <a id="database"></a>[Database](https://supabase.com/docs/guides/database)

Database methods are used to perform basic CRUD operations using the Supabase REST API. Full list of supported operators can be found [here](https://supabase.com/docs/reference/dart/select).
Expand Down Expand Up @@ -365,7 +396,8 @@ You need to setup deep links if you want your native app to open when a user cli
- Magic link login
- Have `confirm email` enabled and are using email login
- Resetting password for email login
- Calling `.signInWithOAuth()` method

`.signInWithOAuth()`, `.signInWithSSO()` and `.linkIdentity()` no longer rely on these deep links. They run inside a system web authentication session that captures the redirect itself, see [OAuth native config](#oauth-native-config).

\*Currently supabase_flutter supports deep links on Android, iOS, Web, MacOS and Windows.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@

#include "generated_plugin_registrant.h"

#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <gtk/gtk_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_to_front/window_to_front_plugin.h>

void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin");
desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar);
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
g_autoptr(FlPluginRegistrar) window_to_front_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowToFrontPlugin");
window_to_front_plugin_register_with_registrar(window_to_front_registrar);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
desktop_webview_window
gtk
url_launcher_linux
window_to_front
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import FlutterMacOS
import Foundation

import app_links
import desktop_webview_window
import flutter_web_auth_2
import shared_preferences_foundation
import url_launcher_macos
import window_to_front

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin"))
FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
#include "generated_plugin_registrant.h"

#include <app_links/app_links_plugin_c_api.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <window_to_front/window_to_front_plugin.h>

void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
DesktopWebviewWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowToFrontPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowToFrontPlugin"));
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

list(APPEND FLUTTER_PLUGIN_LIST
app_links
desktop_webview_window
url_launcher_windows
window_to_front
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
Expand Down
7 changes: 7 additions & 0 deletions packages/supabase_flutter/lib/src/oauth_redirect_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// coverage:ignore-file

/// Navigates the current browser tab to [url].
///
/// Only meaningful on web. The stub throws because native and desktop platforms
/// go through the web auth session instead of a full-page redirect.
void redirectToUrl(String url) => throw UnimplementedError();
5 changes: 5 additions & 0 deletions packages/supabase_flutter/lib/src/oauth_redirect_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:web/web.dart';

/// Navigates the current browser tab to [url], preserving the full-page
/// redirect behavior the OAuth flow relies on for web.
void redirectToUrl(String url) => window.location.assign(url);
98 changes: 59 additions & 39 deletions packages/supabase_flutter/lib/src/supabase_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import 'package:app_links/app_links.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:logging/logging.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:url_launcher/url_launcher.dart';

import './oauth_redirect_stub.dart'
if (dart.library.js_interop) './oauth_redirect_web.dart';

/// Integrates Supabase Auth with the Flutter application lifecycle.
///
Expand Down Expand Up @@ -299,33 +302,20 @@ extension GoTrueClientSignInProvider on GoTrueClient {
OAuthProvider provider, {
String? redirectTo,
String? scopes,
LaunchMode authScreenLaunchMode = LaunchMode.platformDefault,
Map<String, String>? queryParams,
bool preferEphemeral = false,
}) async {
final res = await getOAuthSignInUrl(
provider: provider,
redirectTo: redirectTo,
scopes: scopes,
queryParams: queryParams,
);
final uri = Uri.parse(res.url);

LaunchMode launchMode = authScreenLaunchMode;

// `Platform.isAndroid` throws on web, so adding a guard for web here.
final isAndroid = !kIsWeb && Platform.isAndroid;

// Google login has to be performed on external browser window on Android
if (provider == OAuthProvider.google && isAndroid) {
launchMode = LaunchMode.externalApplication;
}

final result = await launchUrl(
uri,
mode: launchMode,
webOnlyWindowName: '_self',
return _authenticateWithRedirect(
Uri.parse(res.url),
redirectTo: redirectTo,
preferEphemeral: preferEphemeral,
);
return result;
}

/// Attempts a single-sign on using an enterprise Identity Provider. A
Expand All @@ -341,8 +331,9 @@ extension GoTrueClientSignInProvider on GoTrueClient {
/// If you have built an organization-specific login page, you can use the
/// organization's SSO Identity Provider UUID directly instead.
///
/// Returns true if the URL was launched successfully, otherwise either returns
/// false or throws a [PlatformException] depending on the launchUrl failure.
/// On web the current tab is redirected to the identity provider. On every
/// other platform the flow runs inside a system web authentication session
/// and resolves once the session has been established.
///
/// ```dart
/// await supabase.auth.signInWithSSO(
Expand All @@ -354,18 +345,18 @@ extension GoTrueClientSignInProvider on GoTrueClient {
String? domain,
String? redirectTo,
String? captchaToken,
LaunchMode launchMode = LaunchMode.platformDefault,
bool preferEphemeral = false,
}) async {
final ssoUrl = await getSSOSignInUrl(
providerId: providerId,
domain: domain,
redirectTo: redirectTo,
captchaToken: captchaToken,
);
return await launchUrl(
return _authenticateWithRedirect(
Uri.parse(ssoUrl),
mode: launchMode,
webOnlyWindowName: '_self',
redirectTo: redirectTo,
preferEphemeral: preferEphemeral,
);
}

Expand All @@ -380,32 +371,61 @@ extension GoTrueClientSignInProvider on GoTrueClient {
OAuthProvider provider, {
String? redirectTo,
String? scopes,
LaunchMode authScreenLaunchMode = LaunchMode.platformDefault,
Map<String, String>? queryParams,
bool preferEphemeral = false,
}) async {
final res = await getLinkIdentityUrl(
provider,
redirectTo: redirectTo,
scopes: scopes,
queryParams: queryParams,
);
final uri = Uri.parse(res.url);

LaunchMode launchMode = authScreenLaunchMode;
return _authenticateWithRedirect(
Uri.parse(res.url),
redirectTo: redirectTo,
preferEphemeral: preferEphemeral,
);
}

// `Platform.isAndroid` throws on web, so adding a guard for web here.
final isAndroid = !kIsWeb && Platform.isAndroid;
/// Runs an OAuth-style redirect flow for [url].
///
/// On web the current tab is redirected to [url] and the session is picked up
/// when the browser returns to the app. On every other platform the flow runs
/// inside a system web authentication session (`ASWebAuthenticationSession` on
/// Apple platforms, Custom Tabs on Android) which captures the redirect to
/// [redirectTo] and hands it back, so the session is established before this
/// returns. [preferEphemeral] requests an ephemeral session that does not
/// share cookies with the system browser (Apple platforms and Android only).
Future<bool> _authenticateWithRedirect(
Uri url, {
required String? redirectTo,
required bool preferEphemeral,
}) async {
if (kIsWeb) {
redirectToUrl(url.toString());
return true;
}

// Google login has to be performed on external browser window on Android
if (provider == OAuthProvider.google && isAndroid) {
launchMode = LaunchMode.externalApplication;
if (redirectTo == null) {
throw const AuthException(
'redirectTo is required to capture the authentication callback on this '
'platform.',
);
}

final result = await launchUrl(
uri,
mode: launchMode,
webOnlyWindowName: '_self',
final redirectUri = Uri.parse(redirectTo);
final isHttps = redirectUri.scheme == 'https';
final result = await FlutterWebAuth2.authenticate(
url: url.toString(),
callbackUrlScheme: redirectUri.scheme,
options: FlutterWebAuth2Options(
preferEphemeral: preferEphemeral,
httpsHost: isHttps ? redirectUri.host : null,
httpsPath: isHttps ? redirectUri.path : null,
),
);
return result;

await getSessionFromUrl(Uri.parse(result));
return true;
}
}
1 change: 0 additions & 1 deletion packages/supabase_flutter/lib/supabase_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
library supabase_flutter;

export 'package:supabase/supabase.dart';
export 'package:url_launcher/url_launcher.dart' show LaunchMode;

export 'src/flutter_go_true_client_options.dart';
export 'src/local_storage.dart';
Expand Down
4 changes: 3 additions & 1 deletion packages/supabase_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dependencies:
http: '>=0.13.4 <2.0.0'
meta: ^1.7.0
supabase: 2.11.0
url_launcher: ^6.1.2
flutter_web_auth_2: ^5.0.0
path_provider: ^2.0.0
shared_preferences: ^2.0.0
logging: ^1.2.0
Expand All @@ -29,7 +29,9 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
flutter_web_auth_2_platform_interface: ^5.0.0
path: ^1.8.3
plugin_platform_interface: ^2.0.0
web_socket_channel: '>=2.3.0 <4.0.0'

platforms:
Expand Down
Loading
Loading