使用 Logto SDK 自定义 FlutterFlow CustomAuthManager
FlutterFlow 具有内置的 custom authentication 功能,允许你使用自己的后端对用户进行认证。然而,内置的自定义认证流程设计为仅支持单个认证 API 调用。如果你使用第三方身份提供商 (IdP),认证请求只能使用 Resource Owner Password Credentials
授权类型,这不推荐用于生产环境。有关更多详细信息,请参阅 Deprecated ropc grant type。
标准的 OpenID Connect (OIDC) 认证流程涉及多个步骤,例如授权、令牌交换和用户信息检索。要使用像 Logto 这样的 IdP 实现标准的 OIDC 认证流程,你需要在 FlutterFlow 中自定义 CustomAuthManager
类。
本教程将向你展示如何使用 Logto Flutter SDK 自定义 FlutterFlow 中的 CustomAuthManager
类。你可以利用 Logto SDK 实现标准的 OIDC 认证流程,同时保留 FlutterFlow UI 构建器的优势。
- Logto SDK 包可在 pub.dev 和 Logto github repository 上获取。
- SDK 目前仅适用于 Android 和 iOS 平台。
前提条件
- 一个 Logto Cloud 账户或一个 self-hosted Logto。
- 创建一个 Logto Flutter 应用。
- 一个 FlutterFlow 项目。
启用 FlutterFlow 自定义代码
为了自定义 CustomAuthManager
类,你需要在 FlutterFlow 中启用自定义代码功能。按照 Manage Custom Code In GitHub 指南将你的 FlutterFlow 项目与 GitHub 同步。
在 GitHub 中管理自定义代码是 FlutterFlow 的高级功能。你需要将 FlutterFlow 升级到专业计划以启用此功能。
完成后,你将在 GitHub FlutterFlow 仓库下拥有三个不同的分支:
main
:Flutter 项目的主分支。你需要此分支来部署你的项目。flutterflow
:FlutterFlow
将从 FlutterFlow 编辑器同步更改的分支。develop
:你可以在此分支中修改自定义代码。
在 FlutterFlow 中创建你的 UI
首先,在 FlutterFlow 中创建你的 UI。你可以按照 FlutterFlow 文档 根据你的需求创建 UI。对于本教程,最低要求是创建两个页面:
- 一个带有登录按钮的简单主页。
- 一个显示用户信息和注销按钮的用户资料页面。
进入 App Settings
-> Authentication
页面并启用自定义认证。这将在你的 FlutterFlow 项目中创建一个 CustomAuthManager
类。
准备好 UI 后,导航到 integrations
-> GitHub
页面并点击 Push to Repository
按钮,将更改推送到 flutterflow
分支。
自定义 CustomAuthManager
切换到 GitHub 仓库中的 develop
分支,并合并来自 flutterflow
分支的最新更改。包括你的 UI 页面和预构建的 CustomAuthManager
类。
安装 Logto SDK 依赖
将 Logto SDK 依赖添加到你的项目中。
flutter pub add logto_dart_sdk
可选的 Http 包:
Logto 客户端需要一个 http 客户端来进行 API 调用。你可以使用 http
包或任何其他你选择的 http 客户端包。
flutter pub add http
更新 UserProvider
将 OpenIdClaims
类添加到 CustomAuthUserProvider
类中以存储用户信息。
OpenIdClaims
类是 Logto SDK 的一部分,它将为认证用户提供id_token
声明。
// lib/auth/custom_auth/custom_auth_user_provider.dart
import 'package:logto_dart_sdk/src/modules/id_token.dart';
import 'package:rxdart/rxdart.dart';
import 'custom_auth_manager.dart';
class FlutterFlowAuthAuthUser {
FlutterFlowAuthAuthUser({required this.loggedIn, this.uid, this.idToken});
bool loggedIn;
String? uid;
OpenIdClaims? idToken;
}
/// 生成认证用户的流。
BehaviorSubject<FlutterFlowAuthAuthUser> flutterFlowAuthAuthUserSubject =
BehaviorSubject.seeded(FlutterFlowAuthAuthUser(loggedIn: false));
Stream<FlutterFlowAuthAuthUser> flutterFlowAuthAuthUserStream() =>
flutterFlowAuthAuthUserSubject
.asBroadcastStream()
.map((user) => currentUser = user);
在 CustomAuthManager 中初始化 logto 客户端
在 CustomAuthManager
类中初始化 Logto 客户端。
// lib/auth/custom_auth/custom_auth_manager.dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:logto_dart_sdk/logto_client.dart';
import 'package:logto_dart_sdk/src/modules/id_token.dart';
import 'custom_auth_user_provider.dart';
export 'custom_auth_manager.dart';
class CustomAuthManager {
late LogtoClient logtoClient;
final logtoConfig = const LogtoConfig(
appId: '<YOUR-APP-ID>',
endpoint: '<YOUR-LOGTO-ENDPOINT>');
// ...
FlutterFlowAuthAuthUser? _updateCurrentUser(
{bool loggedIn = false, String? uid, OpenIdClaims? idToken}) {
// 更新当前用户流。
final updatedUser = FlutterFlowAuthAuthUser(
loggedIn: loggedIn,
uid: uid,
idToken: idToken,
);
flutterFlowAuthAuthUserSubject.add(updatedUser);
return updatedUser;
}
Future initialize() async {
logtoClient = LogtoClient(config: logtoConfig, httpClient: http.Client());
late OpenIdClaims? idToken;
try {
idToken = await logtoClient.idTokenClaims;
} catch (e) {
if (kDebugMode) {
print('初始化认证时出错: $e');
}
}
_updateCurrentUser(
loggedIn: idToken != null, uid: idToken?.subject, idToken: idToken);
}
}
FlutterFlowAuthAuthUser? currentUser;
bool get loggedIn => currentUser?.loggedIn ?? false;
initialize
方法将初始化 Logto 客户端,并使用本地存储中持久化的用户认证状态更新当前用户流。
Logto SDK 使用 flutter_secure_storage 包来安全地存储用户认证信息。
实现登录方法
调用 LogtoClient.signIn
方法将启动标准的 OIDC 认证流程。Logto 登录页面将在 webview 中使用 flutter_web_auth 打开。
// lib/auth/custom_auth/custom_auth_manager.dart
Future<FlutterFlowAuthAuthUser?> signIn(
String redirectUri,
) async {
await logtoClient.signIn(redirectUri);
var idTokenClaims = await logtoClient.idTokenClaims;
return _updateCurrentUser(
loggedIn: idTokenClaims != null,
uid: idTokenClaims?.subject,
idToken: idTokenClaims,
);
}
LogtoClient 将处理授权、令牌交换和用户信息检索步骤。一旦用户认证成功,idTokenClaims
将存储在本地存储中。
从 LogtoClient 中检索 idTokenClaims
并更新当前用户流。
实现注销方法
// lib/auth/custom_auth/custom_auth_manager.dart
Future signOut() async {
await logtoClient.signOut();
flutterFlowAuthAuthUserSubject.add(
FlutterFlowAuthAuthUser(loggedIn: false),
);
}
更新认证工具方法
- 添加
authManager
getter 以访问CustomAuthManager
实例。 - 添加
currentUserUid
getter 以获取当前用户 uid。 - 添加
currentUserData
getter 以获取当前用户数据。 - 添加
logtoClient
getter 以访问 Logto 客户端实例。
// lib/auth/custom_auth/auth_util.dart
import 'package:logto_dart_sdk/logto_client.dart';
import 'package:logto_dart_sdk/src/modules/id_token.dart';
import 'custom_auth_manager.dart';
export 'custom_auth_manager.dart';
final _authManager = CustomAuthManager();
CustomAuthManager get authManager => _authManager;
String get currentUserUid => currentUser?.uid ?? '';
OpenIdClaims? get currentUserData => currentUser?.idToken;
LogtoClient get logtoClient => _authManager.logtoClient;
在你的 UI 中集成自定义认证
主页
当用户点击登录按钮时,调用 authManager.signIn
方法以启 动认证流程。
redirectUri
是用于捕获来自 Logto 登录页面的授权回调的回调 URL。 有关 redirectUri 的更多详细信息,请参阅 Flutter SDK。
// lib/pages/home_page/home_page_widget.dart
final redirectUri = 'io.logto://callback';
// ...
FFButtonWidget(
onPressed: () async {
GoRouter.of(context).prepareAuthEvent();
await authManager.signIn(redirectUri);
context.replaceNamed('user');
},
text: 'Sign In',
// ...
)
用户资料页面
使用认证工具 getter 访问当前用户数据和 Logto 客户端实例。
// lib/pages/user/user_widget.dart
import '/auth/custom_auth/auth_util.dart';
// ...
children: [
Text(
'User ID: $currentUserUid',
),
Text(
'Display Name: ${currentUserData?.name}',
),
Text(
'Username: ${currentUserData?.username}',
),
Text(
'Email: ${currentUserData?.emailVerified ?? currentUserData?.email}',
),
]
当用户点击注销按钮时,实现注销方法。
// lib/pages/user/user_widget.dart
FFButtonWidget(
onPressed: () async {
await authManager.signOut();
context.replaceNamed('HomePage');
},
text: 'Sign Out',
// ...
)
进一步阅读
Logto SDK 提供了更多与 Logto API 交互的方法。你可以进一步自定义 CustomAuthManager
类以实现更多功能。
依赖问题排查
flutter_secure_storage
我们使用 flutter_secure_storage 来实现跨平台的持久安全令牌存储。其底层实现:
- iOS 使用 Keychain
- Android 使用 AES 加密。
配置 Android 版本:
在 [project]/android/app/build.gradle 中将 minSdkVersion 设置为 >= 18。
android {
...
defaultConfig {
...
minSdkVersion 18
...
}
}
禁用自动备份:
默认情况下,Android 会在 Google Drive 上备份数据。这可能导致异常 java.security.InvalidKeyException:Failed to unwrap key。
为避免这种情况,你可以禁用应用的自动备份或从 FlutterSecureStorage 中排除 sharedprefs。
-
要禁用自动备份,请转到应用的 manifest 文件并设置布尔值 android:allowBackup:
<manifest ... >
...
<application
android:allowBackup="false"
android:fullBackupContent="false"
...
>
...
</application>
</manifest> -
从 FlutterSecureStorage 中排除 sharedprefs。
如果你需要为应用启用 android:fullBackupContent。设置一个备份规则以 排除 插件使用的 prefs:
<application ...
android:fullBackupContent="@xml/backup_rules">
</application><?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>请查看 flutter_secure_storage 了解更多详情。
flutter_web_auth
flutter_web_auth 在 Logto 的 flutter SDK 中使用。我们依赖其基于 webview 的交互界面来打开 Logto 的授权页面。
这个插件在 iOS 12+ 和 macOS 10.15+ 上使用 ASWebAuthenticationSession,在 iOS 11 上使用 SFAuthenticationSession,在 Android 上使用 Chrome Custom Tabs,并在 Web 上打开一个新窗口。 你可以在 iOS 8+ 上构建它,但目前仅支持 iOS 11 或更高版本。
在 Android 上注册回调 url
为了从 Logto 的登录网页捕获回调 url,你需要将你的登录 redirectUri 注册到 AndroidManifest.xml 中。
<activity android:name="com.linusu.flutter_web_auth.CallbackActivity" android:exported="true">
<intent-filter android:label="flutter_web_auth">
<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.logto"/>
</intent-filter>
</activity>