跳到主要内容

使用 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 构建器的优势。

提示:

前提条件

启用 FlutterFlow 自定义代码

为了自定义 CustomAuthManager 类,你需要在 FlutterFlow 中启用自定义代码功能。按照 Manage Custom Code In GitHub 指南将你的 FlutterFlow 项目与 GitHub 同步。

备注:

在 GitHub 中管理自定义代码是 FlutterFlow 的高级功能。你需要将 FlutterFlow 升级到专业计划以启用此功能。

完成后,你将在 GitHub FlutterFlow 仓库下拥有三个不同的分支:

  1. main:Flutter 项目的主分支。你需要此分支来部署你的项目。
  2. flutterflowFlutterFlow 将从 FlutterFlow 编辑器同步更改的分支。
  3. develop:你可以在此分支中修改自定义代码。

在 FlutterFlow 中创建你的 UI

首先,在 FlutterFlow 中创建你的 UI。你可以按照 FlutterFlow 文档 根据你的需求创建 UI。对于本教程,最低要求是创建两个页面:

  1. 一个带有登录按钮的简单主页。
  2. 一个显示用户信息和注销按钮的用户资料页面。

进入 App Settings -> Authentication 页面并启用自定义认证。这将在你的 FlutterFlow 项目中创建一个 CustomAuthManager 类。

FlutterFlow custom authentication

准备好 UI 后,导航到 integrations -> GitHub 页面并点击 Push to Repository 按钮,将更改推送到 flutterflow 分支。

FlutterFlow GitHub push

自定义 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。

  1. 要禁用自动备份,请转到应用的 manifest 文件并设置布尔值 android:allowBackup

    <manifest ... >
    ...
    <application
    android:allowBackup="false"
    android:fullBackupContent="false"
    ...
    >
    ...
    </application>
    </manifest>

  2. 从 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>