為你的 Flutter 應用程式新增驗證 (Authentication)
本教程將向你展示如何將 Logto 整合到你的 Flutter 應用程式中。
- SDK 套件可在 pub.dev 和 Logto GitHub 儲存庫 上取得。
- 範例專案使用 Flutter material 建立。你可以在 pub.dev 上找到它。
- 此 SDK 與 iOS、Android 和 Web 平台上的 Flutter 應用程式相容。其他平台的相容性尚未測試。
先決條件
- 一個 Logto Cloud 帳戶或 自行託管的 Logto。
- 已創建的 Logto 原生應用程式。
- Flutter 或 Dart 開發環境。
安裝
- pub.dev
- GitHub
你可以使用 pub 套件管理器直接安裝 logto_dart_sdk package。在你的專案根目錄下執行以下命令:
flutter pub add logto_dart_sdk
或者將以下內容新增到你的 pubspec.yaml 檔案中:
dependencies:
logto_dart_sdk: ^3.0.0
然後執行:
flutter pub get
如果你希望分叉自己的 SDK 版本,可以直接從 GitHub 克隆儲存庫。
git clone https://github.com/logto-io/dart
設定
SDK 版本相容性
| Logto SDK 版本 | Dart SDK 版本 | Dart 3.0 相容性 |
|---|---|---|
| < 2.0.0 | >= 2.17.6 < 3.0.0 | false |
| >= 2.0.0 < 3.0.0 | >= 3.0.0 | true |
| >= 3.0.0 | >= 3.6.0 | true |
flutter_secure_storage 設定
在底層,這個 SDK 使用 flutter_secure_storage 來實現跨平台的持久安全權杖存儲。
- iOS 使用 Keychain
- Android 使用 AES 加密
配置 Android 版本
在專案的 android/app/build.gradle 文件中將 android:minSdkVersion 設定為 >= 18。
android {
...
defaultConfig {
...
minSdkVersion 18
...
}
}
在 Android 上禁用自動備份
預設情況下,Android 會在 Google Drive 上備份資料。這可能會導致例外 java.security.InvalidKeyException:Failed 解包密鑰。為避免此情況,
-
要禁用自動備份,請到應用程式的 manifest 文件中,將
android:allowBackup和android:fullBackupContent屬性設為false。AndroidManifest.xml<manifest ... >
...
<application
android:allowBackup="false"
android:fullBackupContent="false"
...
>
...
</application>
</manifest> -
從
FlutterSecureStorage中排除sharedprefs。如果你需要保留應用程式的
android:fullBackupContent而不是禁用它,你可以從備份中排除sharedprefs目錄。 更多詳情請參閱 Android 文檔。在你的 AndroidManifest.xml 文件中,將 android:fullBackupContent 屬性添加到
<application>元素,如下例所示。此屬性指向包含備份規則的 XML 文件。AndroidManifest.xml<application ...
android:fullBackupContent="@xml/backup_rules">
</application>在
res/xml/目錄中創建一個名為@xml/backup_rules的 XML 文件。在此文件中,使用<include>和<exclude>元素添加規則。以下範例備份所有共享偏好設定,除了 device.xml:@xml/backup_rules<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>
請查看 flutter_secure_storage 以獲取更多詳情。
flutter_web_auth_2 設定
在底層,這個 SDK 使用 flutter_web_auth_2 來驗證使用者與 Logto。此套件提供了一種簡單的方法,使用系統 webview 或瀏覽器來驗證使用者與 Logto。
此插件在 iOS 12+ 和 macOS 10.15+ 上使用 ASWebAuthenticationSession,在 iOS 11 上使用 SFAuthenticationSession,在 Android 上使用 Chrome Custom Tabs,並在 Web 上開啟新窗口。
-
iOS:無需額外設定
-
Android:在 Android 上註冊回調 URL
為了從 Logto 的登入網頁捕獲回調 URL,你需要將你的登入 redirectUri 註冊到
AndroidManifest.xml文件中。AndroidManifest.xml<manifest>
<application>
<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="YOUR_CALLBACK_URL_SCHEME_HERE" />
</intent-filter>
</activity>
</application>
</manifest> -
網頁瀏覽器:創建一個端點來處理回調 URL
如果你使用的是網頁平台,你需要創建一個端點來處理回調 URL,並使用
postMessageAPI 將其發送回應用程式。callback.html<!doctype html>
<title>驗證完成</title>
<p>驗證已完成。如果未自動發生,請關閉窗口。</p>
<script>
function postAuthenticationMessage() {
const message = {
'flutter-web-auth-2': window.location.href,
};
if (window.opener) {
window.opener.postMessage(message, window.location.origin);
window.close();
} else if (window.parent && window.parent !== window) {
window.parent.postMessage(message, window.location.origin);
} else {
localStorage.setItem('flutter-web-auth-2', window.location.href);
window.close();
}
}
postAuthenticationMessage();
</script>
請查看 flutter_web_auth_2 套件中的設定指南以獲取更多詳情。
整合
初始化 LogtoClient
匯入 logto_dart_sdk 套件並在應用程式的根目錄初始化 LogtoClient 實例。
import 'package:logto_dart_sdk/logto_dart_sdk.dart';
import 'package:http/http.dart' as http;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Logto Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late LogtoClient logtoClient;
void render() {
// 狀態變更
}
// LogtoConfig
final logtoConfig = const LogtoConfig(
endpoint: "<your-logto-endpoint>",
appId: "<your-app-id>"
);
void _init() {
logtoClient = LogtoClient(
config: logtoConfig,
httpClient: http.Client(), // 可選的 http client
);
render();
}
void initState() {
super.initState();
_init();
}
// ...
}
實作登入功能
在進入細節之前,這裡先快速說明一下終端使用者的體驗。登入流程可簡化如下:
- 你的應用程式呼叫登入方法。
- 使用者被重新導向至 Logto 登入頁面。對於原生應用程式,會開啟系統瀏覽器。
- 使用者登入後,會被重新導向回你的應用程式(設定為 redirect URI)。
關於基於重導的登入
- 此驗證流程遵循 OpenID Connect (OIDC) 協議,Logto 強制執行嚴格的安全措施以保護使用者登入。
- 如果你有多個應用程式,可以使用相同的身分提供者 (IdP, Identity provider)(Logto)。一旦使用者登入其中一個應用程式,Logto 將在使用者訪問另一個應用程式時自動完成登入流程。
欲了解更多關於基於重導登入的原理和優勢,請參閱 Logto 登入體驗解析。
開始之前,你需要在管理控制台為你的應用程式新增一個 redirect URI。
讓我們切換到 Logto Console 的應用程式詳細資訊頁面。新增一個重定向 URI io.logto://callback,然後點擊「儲存變更」。
- 對於 iOS,redirect URI 的 scheme 並不重要,因為
ASWebAuthenticationSession類別會監聽 redirect URI,而不論其是否已註冊。 - 對於 Android,redirect URI 的 scheme 必須在
AndroidManifest.xml文件中註冊。
配置好 redirect URI 後,我們在頁面上新增一個登入按鈕,該按鈕將調用 logtoClient.signIn API 來啟動 Logto 登入流程:
class _MyHomePageState extends State<MyHomePage> {
// ...
final redirectUri = 'io.logto://callback';
Widget build(BuildContext context) {
// ...
Widget signInButton = TextButton(
onPressed: () async {
await logtoClient.signIn(redirectUri);
render();
},
child: const Text('Sign In'),
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SelectableText('My Demo App'),
signInButton,
],
),
),
);
}
}
實作登出功能
讓我們切換到 Logto Console 的應用程式詳細資訊頁面。新增一個登出後重新導向 URI
io.logto://callback,然後點擊「儲存變更」。
登出後重新導向 URI 是一個 OAuth 2.0 概念,表示登出後應重新導向的位置。
現在讓我們在主頁面上新增一個登出按鈕,以便使用者可以從你的應用程式登出。
class _MyHomePageState extends State<MyHomePage> {
// ...
final postSignOutRedirectUri = 'io.logto//home';
Widget build(BuildContext context) {
// ...
Widget signOutButton = TextButton(
onPressed: () async {
await logtoClient.signOut(postSignOutRedirectUri);
render();
},
child: const Text('Sign Out'),
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SelectableText('My Demo App'),
signInButton,
signOutButton,
],
),
),
);
}
}
處理驗證 (Authentication) 狀態
Logto SDK 提供了一個非同步方法來檢查驗證 (Authentication) 狀態。該方法是 logtoClient.isAuthenticated。如果使用者已驗證,該方法返回布林值 true,否則返回 false。
在範例中,我們根據驗證 (Authentication) 狀態有條件地渲染登入和登出按鈕。現在讓我們更新 Widget 中的 render 方法來處理狀態變更:
class _MyHomePageState extends State<MyHomePage> {
// ...
bool? isAuthenticated = false;
void render() {
setState(() async {
isAuthenticated = await logtoClient.isAuthenticated;
});
}
Widget build(BuildContext context) {
// ...
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SelectableText('My Demo App'),
isAuthenticated == true ? signOutButton : signInButton,
],
),
),
);
}
}
檢查點:測試你的應用程式
現在,你可以測試你的應用程式:
- 執行你的應用程式,你會看到登入按鈕。
- 點擊登入按鈕,SDK 會初始化登入流程並將你重定向到 Logto 登入頁面。
- 登入後,你將被重定向回應用程式並看到登出按鈕。
- 點擊登出按鈕以清除權杖存儲並登出。
獲取使用者資訊
顯示使用者資訊
要顯示使用者資訊,你可以使用 logtoClient.idTokenClaims getter。例如,在 Flutter 應用程式中:
class _MyHomePageState extends State<MyHomePage> {
// ...
Widget build(BuildContext context) {
// ...
Widget getUserInfoButton = TextButton(
onPressed: () async {
final userClaims = await logtoClient.idTokenClaims;
print(userInfo);
},
child: const Text('Get user info'),
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SelectableText('My Demo App'),
isAuthenticated == true ? signOutButton : signInButton,
isAuthenticated == true ? getUserInfoButton : const SizedBox.shrink(),
],
),
),
);
}
}
請求額外的宣告 (Claims)
你可能會發現從 client.idTokenClaims 返回的物件中缺少一些使用者資訊。這是因為 OAuth 2.0 和 OpenID Connect (OIDC) 的設計遵循最小權限原則 (PoLP, Principle of Least Privilege),而 Logto 是基於這些標準構建的。
預設情況下,僅返回有限的宣告 (Claims)。如果你需要更多資訊,可以請求額外的權限範圍 (Scopes) 以存取更多宣告。
「宣告 (Claim)」是對主體所做的斷言;「權限範圍 (Scope)」是一組宣告。在目前的情況下,宣告是關於使用者的一部分資訊。
以下是權限範圍與宣告關係的非規範性範例:
「sub」宣告表示「主體 (Subject)」,即使用者的唯一識別符(例如使用者 ID)。
Logto SDK 將始終請求三個權限範圍:openid、profile 和 offline_access。
要請求額外的權限範圍 (Scopes),可以將權限範圍傳遞給 LogtoConfig 物件。例如:
// LogtoConfig
final logtoConfig = const LogtoConfig(
endpoint: "<your-logto-endpoint>",
appId: "<your-app-id>",
scopes: ["email", "phone"],
);
我們也在 SDK 套件中提供了一個內建的 LogtoUserScope 列舉,幫助你使用預定義的權限範圍 (Scopes)。
// LogtoConfig
final logtoConfig = const LogtoConfig(
endpoint: "<your-logto-endpoint>",
appId: "<your-app-id>",
scopes: [LogtoUserScope.email.value, LogtoUserScope.phone.value],
);
需要網路請求的宣告 (Claims)
為了防止 ID 權杖 (ID token) 膨脹,某些宣告 (Claims) 需要透過網路請求來獲取。例如,即使在權限範圍 (Scopes) 中請求了 custom_data 宣告,它也不會包含在使用者物件中。要存取這些宣告,你可以使用 logtoClient.getUserInfo() 方法:
class _MyHomePageState extends State<MyHomePage> {
// ...
Widget build(BuildContext context) {
// ...
Widget getUserInfoButton = TextButton(
onPressed: () async {
final userInfo = await logtoClient.getUserInfo();
print(userInfo);
},
child: const Text('取得使用者資訊'),
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SelectableText('我的示範應用程式'),
isAuthenticated == true ? signOutButton : signInButton,
isAuthenticated == true ? getUserInfoButton : const SizedBox.shrink(),
],
),
),
);
}
}
權限範圍 (Scopes) 和宣告 (Claims)
Logto 採用 OIDC 權限範圍 (Scopes) 與宣告 (Claims) 慣例 來定義從 ID 權杖 (ID token) 及 OIDC userinfo 端點 取得使用者資訊時的權限範圍與宣告。無論「權限範圍 (Scope)」還是「宣告 (Claim)」,皆為 OAuth 2.0 與 OpenID Connect (OIDC) 規範中的術語。
對於標準 OIDC 宣告 (Claims),其是否包含於 ID 權杖 (ID token) 內,完全取決於所請求的權限範圍 (Scopes)。擴充宣告(如 custom_data 與 organizations)則可透過 自訂 ID 權杖 (Custom ID token) 設定,額外配置於 ID 權杖中。
以下是支援的權限範圍 (Scopes) 及對應的宣告 (Claims) 清單:
標準 OIDC 權限範圍 (Scopes)
openid(預設)
| Claim name | Type | Description |
|---|---|---|
| sub | string | 使用者的唯一識別符 (The unique identifier of the user) |
profile(預設)
| Claim name | Type | Description |
|---|---|---|
| name | string | 使用者全名 (The full name of the user) |
| username | string | 使用者名稱 (The username of the user) |
| picture | string | 終端使用者大頭貼的 URL。此 URL 必須指向圖片檔案(如 PNG、JPEG 或 GIF),而非包含圖片的網頁。請注意,此 URL 應明確指向適合描述終端使用者的個人照片,而非任意由終端使用者拍攝的照片。(URL of the End-User's profile picture. This URL MUST refer to an image file (for example, a PNG, JPEG, or GIF image file), rather than to a Web page containing an image. Note that this URL SHOULD specifically reference a profile photo of the End-User suitable for displaying when describing the End-User, rather than an arbitrary photo taken by the End-User.) |
| created_at | number | 終端使用者建立時間。以自 Unix epoch(1970-01-01T00:00:00Z)以來的毫秒數表示。(Time the End-User was created. The time is represented as the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).) |
| updated_at | number | 終端使用者資訊最後更新時間。以自 Unix epoch(1970-01-01T00:00:00Z)以來的毫秒數表示。(Time the End-User's information was last updated. The time is represented as the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).) |
其他 標準宣告 (Standard claims) 包含 family_name、given_name、middle_name、nickname、preferred_username、profile、website、gender、birthdate、zoneinfo 及 locale 也會包含在 profile 權限範圍內,無需額外請求 userinfo endpoint。與上表宣告不同的是,這些宣告僅在其值不為空時才會回傳,而上表宣告若值為空則會回傳 null。
與標準宣告不同,created_at 與 updated_at 宣告使用毫秒而非秒為單位。
email
| Claim name | Type | Description |
|---|---|---|
string | 使用者的電子郵件地址 (The email address of the user) | |
| email_verified | boolean | 電子郵件地址是否已驗證 (Whether the email address has been verified) |
phone
| Claim name | Type | Description |
|---|---|---|
| phone_number | string | 使用者的電話號碼 (The phone number of the user) |
| phone_number_verified | boolean | 電話號碼是否已驗證 (Whether the phone number has been verified) |
address
請參閱 OpenID Connect Core 1.0 以瞭解 address 宣告的詳細資訊。
標註為 (預設) 的權限範圍 (Scopes) 會由 Logto SDK 自動請求。當請求對應權限範圍時,標準 OIDC 權限範圍下的宣告 (Claims) 會始終包含於 ID 權杖 (ID token) 中,且無法關閉。
擴充權限範圍 (Extended scopes)
以下權限範圍由 Logto 擴充,會透過 userinfo endpoint 回傳宣告 (Claims)。這些宣告也可透過 Console > Custom JWT 設定直接包含於 ID 權杖 (ID token) 中。詳情請參閱 自訂 ID 權杖 (Custom ID token)。
custom_data
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| custom_data | object | 使用者的自訂資料 (The custom data of the user) |
identities
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| identities | object | 使用者的連結身分 (The linked identities of the user) | |
| sso_identities | array | 使用者的連結 SSO 身分 (The linked SSO identities of the user) |
roles
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| roles | string[] | 使用者的角色 (The roles of the user) | ✅ |
urn:logto:scope:organizations
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| organizations | string[] | 使用者所屬的組織 ID (The organization IDs the user belongs to) | ✅ |
| organization_data | object[] | 使用者所屬的組織資料 (The organization data the user belongs to) |
這些組織宣告 (Organization claims) 也可在使用 不透明權杖 (Opaque token) 時,透過 userinfo endpoint 取得。然而,不透明權杖無法作為組織權杖 (Organization tokens) 來存取組織專屬資源。詳見 不透明權杖與組織 (Opaque token and organizations)。
urn:logto:scope:organization_roles
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| organization_roles | string[] | 使用者所屬組織角色,格式為 <organization_id>:<role_name> (The organization roles the user belongs to with the format of <organization_id>:<role_name>) | ✅ |
API 資源與組織
我們建議先閱讀 🔐 角色型存取控制 (RBAC, Role-Based Access Control),以瞭解 Logto RBAC 的基本概念以及如何正確設定 API 資源。
配置 Logto 用戶端
一旦你設定了 API 資源,就可以在應用程式中配置 Logto 時新增它們:
// LogtoConfig
final logtoConfig = const LogtoConfig(
endpoint: "<your-logto-endpoint>",
appId: "<your-app-id>",
// 新增你的 API 資源
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"],
);
每個 API 資源都有其自身的權限(權限範圍)。
例如,https://shopping.your-app.com/api 資源具有 shopping:read 和 shopping:write 權限,而 https://store.your-app.com/api 資源具有 store:read 和 store:write 權限。
要請求這些權限,你可以在應用程式中配置 Logto 時新增它們:
// LogtoConfig
final logtoConfig = const LogtoConfig(
endpoint: "<your-logto-endpoint>",
appId: "<your-app-id>",
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"],
// 新增你的 API 資源的權限範圍 (scopes)
scopes: ["shopping:read", "shopping:write", "store:read", "store:write"]
);
你可能會注意到權限範圍是獨立於 API 資源定義的。這是因為 OAuth 2.0 的資源標示符 (Resource Indicators) 指定請求的最終權限範圍將是所有目標服務中所有權限範圍的笛卡兒積。
因此,在上述情況中,權限範圍可以從 Logto 的定義中簡化,兩個 API 資源都可以擁有 read 和 write 權限範圍而不需要前綴。然後,在 Logto 配置中:
// LogtoConfig
final logtoConfig = const LogtoConfig(
endpoint: "<your-logto-endpoint>",
appId: "<your-app-id>",
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"],
// 所有資源共用的權限範圍 (Scopes)
scopes: ["read", "write"]
);
對於每個 API 資源,它將請求 read 和 write 權限範圍。
請求未在 API 資源中定義的權限範圍是可以的。例如,即使 API 資源中沒有可用的 email 權限範圍,你也可以請求 email 權限範圍。不可用的權限範圍將被安全地忽略。
成功登入後,Logto 將根據使用者的角色向 API 資源發出適當的權限範圍。
取得 API 資源的存取權杖
要獲取特定 API 資源的存取權杖 (Access token),你可以使用 getAccessToken 方法:
Future<AccessToken?> getAccessToken(String resource) async {
var token = await logtoClient.getAccessToken(resource: resource);
return token;
}
此方法將返回一個 JWT 存取權杖 (Access token),當使用者擁有相關權限時,可以用來存取 API 資源。如果當前快取的存取權杖 (Access token) 已過期,此方法將自動嘗試使用重新整理權杖 (Refresh token) 獲取新的存取權杖 (Access token)。
為組織取得存取權杖
就像 API 資源一樣,你也可以為組織請求存取權杖。當你需要存取使用組織權限範圍而非 API 資源權限範圍定義的資源時,這會很有用。
如果你對組織 (Organization) 不熟悉,請閱讀 🏢 組織(多租戶,Multi-tenancy) 以開始了解。
在配置 Logto client 時,你需要新增 LogtoUserScope.Organizations 權限範圍 (scope):
// LogtoConfig
final logtoConfig = const LogtoConfig(
endpoint: "<your-logto-endpoint>",
appId: "<your-app-id>",
scopes: [LogtoUserScopes.organizations.value]
);
使用者登入後,你可以為使用者獲取組織權杖 (organization token):
// 使用者的有效組織 ID 可以在 ID 權杖 (ID token) 宣告 (claim) `organizations` 中找到。
Future<AccessToken?> getOrganizationAccessToken(String organizationId) async {
var token = await logtoClient.getOrganizationToken(organizationId);
return token;
}
遷移指南
如果你從 Logto Dart SDK 的舊版本遷移,版本 < 3.0.0:
-
更新你的
pubspec.yaml文件以使用最新版本的 Logto Dart SDK。pubspec.yamldependencies:
logto_dart_sdk: ^3.0.0 -
更新 Android manifest 文件,將舊的
flutter_web_auth回調活動替換為新的flutter_web_auth_2。FlutterWebAuth->FlutterWebAuth2flutter_web_auth->flutter_web_auth_2
-
將
redirectUri傳遞給signOut方法。現在在調用
signOut方法時需要redirectUri。對於 iOS 平台,此參數無用,但對於需要額外end_session請求來清理登入會話的 Android 和 Web 平台,此參數將作為end_session請求中的post_logout_redirect_uri參數使用。await logtoClient.signOut(redirectUri);
疑難排解
Android 疑難排解
-
你需要更新你的 AndroidManifest.xml 以包含
com.linusu.flutter_web_auth_2.CallbackActivity活動,如下所示:android/app/src/main/AndroidManifest.xml<manifest>
<application>
<!-- 添加 com.linusu.flutter_web_auth_2.CallbackActivity 活動 -->
<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="YOUR_CALLBACK_URL_SCHEME_HERE" />
</intent-filter>
</activity>
</application>
</manifest> -
如果你的目標是 S+(SDK 版本 31 及以上),你需要為
android:exported提供明確的值。如果你遵循了早期的安裝說明,這一點可能未包含。確保在你的AndroidManifest.xml文件中的com.linusu.flutter_web_auth.CallbackActivity活動中添加android:exported="true"。 -
成功登入後瀏覽器未關閉:
為確保
flutter_web_auth_2正常工作,你需要從AndroidManifest.xml文件中移除任何android:taskAffinity條目。將android:launchMode="singleTop"設置為你的AndroidManifest.xml文件中的主活動。android/app/src/main/AndroidManifest.xml<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
// ...
/>