Skip to main content

Customize the FlutterFlow CustomAuthManager using Logto SDK

FlutterFlow has a built-in custom authentication feature that allows you to authenticate users using your own backend. However, the build-in custom authentication flow was designed to work with a single authentication API call. If you are using a third-party Identity Provider (IdP) the authentication request can only be done using the Resource Owner Password Credentials grant type, which is not recommended for production use. See Deprecated ropc grant type for more details.

A standard OpenID Connect (OIDC) authentication flow involves multiple steps, such as authorization, token exchange, and user information retrieval. To implement a standard OIDC authentication flow with a IdP like Logto, you need to customize the CustomAuthManager class in FlutterFlow.

This tutorial will show you how to customize the CustomAuthManager class in FlutterFlow using Logto Flutter SDK. You may take advantage of the Logto SDK for the standard OIDC authentication flow while keeping the benefits of the FlutterFlow UI builder.

tip
  • Logto SDK package is available on pub.dev and Logto github repository.
  • The SDK is currently only suitable for Android and iOS platforms.

Prerequisites

  • A Logto Cloud account or a self-hosted Logto (Check out the ⚡ Get started guide to create one if you don't have).
  • Create a Logto Flutter application.
  • A FlutterFlow project.

Enable FlutterFlow custom code

In order to customize the CustomAuthManager class, you need to enable the custom code feature in FlutterFlow. Following the Manage Custom Code In GitHub guide to sync your FlutterFlow project with GitHub.

note

Manage custom code in GitHub is a premium feature in FlutterFlow. You need to upgrade your FlutterFlow to pro plan to enable this feature.

Once it is done, you will have three different branches under your GitHub FlutterFlow repository:

  1. main: The main branch for the flutter project. You will need this branch to deploy your project.
  2. flutterflow: The branch where the FlutterFlow will sync the changes from the FlutterFlow editor.
  3. develop: The branch where you can modify your custom code.

Create your UI in FlutterFlow

First, create your UI in FlutterFlow. You can follow the FlutterFlow documentation to create your UI based on your requirements. For this tutorial, for the minimum requirement, we will create two pages:

  1. A simple home page with a login button.
  2. A user profile page to display user information and logout button.

Got to the App Settings -> Authentication page and enable the custom authentication. This will create a CustomAuthManager class in your FlutterFlow project.

FlutterFlow custom authentication

Once you have the UI ready, navigate to the integrations -> GitHub page and click on the Push to Repository button to push the changes to the flutterflow branch.

FlutterFlow GitHub push

Customize the CustomAuthManager

Switch to the develop branch in your GitHub repository and merge the latest changes from the flutterflow branch. Including your UI pages, and the pre built CustomAuthManager class.

Install Logto SDK dependency

Add the Logto SDK dependency to your project.

  flutter pub add logto_dart_sdk
note

Optional Http package:

Logto client requires a http client to make API calls. You can use the http package or any other http client package of your choice.

  flutter pub add http

Update the UserProvider

Add the OpenIdClaims class to the CustomAuthUserProvider class to store the user information.

OpenIdClaims class is a part of the Logto SDK, which will provide the id_token claims for the authenticated user.

// 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;
}

/// Generates a stream of the authenticated user.
BehaviorSubject<FlutterFlowAuthAuthUser> flutterFlowAuthAuthUserSubject =
BehaviorSubject.seeded(FlutterFlowAuthAuthUser(loggedIn: false));
Stream<FlutterFlowAuthAuthUser> flutterFlowAuthAuthUserStream() =>
flutterFlowAuthAuthUserSubject
.asBroadcastStream()
.map((user) => currentUser = user);

Init the logto client in CustomAuthManager

Initialize the Logto client in the CustomAuthManager class.

// 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}) {
// Update the current user stream.
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('Error initializing auth: $e');
}
}

_updateCurrentUser(
loggedIn: idToken != null, uid: idToken?.subject, idToken: idToken);
}
}

FlutterFlowAuthAuthUser? currentUser;
bool get loggedIn => currentUser?.loggedIn ?? false;

The initialize method will initialize the Logto client and update the current user stream with the user authentication status persisted in the local storage.

Logto SDK uses the flutter_secure_storage package to store the user authentication information securely.

Implement the Sign-in method

Call the LogtoClient.signIn method will initiate a standard OIDC authentication flow. The Logto Sign-In page will be opened in a webview using 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 will handle the authorization, token exchange, and user information retrieval steps. Once the user is authenticated, the idTokenClaims will be stored in the local storage. Retrieve the idTokenClaims from the LogtoClient and update the current user stream.

Implement the Sign-out method

// lib/auth/custom_auth/custom_auth_manager.dart

Future signOut() async {
await logtoClient.signOut();

flutterFlowAuthAuthUserSubject.add(
FlutterFlowAuthAuthUser(loggedIn: false),
);
}

Update the auth util methods

  • Add the authManager getter to access the CustomAuthManager instance.
  • Add the currentUserUid getter to get the current user uid.
  • Add the currentUserData getter to get the current user data.
  • Add the logtoClient getter to access the Logto client instance.
// 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;

Integrate the custom authentication in your UI

Home Page

Call the authManager.signIn method to initiate the authentication flow when the user clicks on the sign-in button.

redirectUri is the callback URL that will be used to capture the authorization callback from the Logto sign-in page. See the Flutter SDK for more details on the redirectUri.

// 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',
// ...
)

User Profile Page

Use the auth util getters to access the current user data and the Logto client instance.

// 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}',
),
]

Implement the sign-out method when the user clicks on the sign-out button.

// lib/pages/user/user_widget.dart

FFButtonWidget(
onPressed: () async {
await authManager.signOut();

context.replaceNamed('HomePage');
},
text: 'Sign Out',
// ...
)

Further readings

Logto SDK provides more methods to interact with the Logto API. You may further customize the CustomAuthManager class to implement more features.

Dependency troubleshooting

flutter_secure_storage

We use flutter_secure_storage to implement the cross-platform persistent secure token storage. Under the hood:

  • Keychain is used for iOS
  • AES encryption is used for Android.

Config Android version:

In [project]/android/app/build.gradle set minSdkVersion to >= 18.

  android {
...

defaultConfig {
...
minSdkVersion 18
...
}
}

Disable autobackup:

note

By default Android backups data on Google Drive. It can cause exception java.security.InvalidKeyException:Failed to unwrap key.

To avoid this, you can disable auto backup for your app or exclude sharedprefs from the FlutterSecureStorage.

  1. To disable auto backup, go to your app manifest file and set the boolean value android:allowBackup:

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

  2. Exclude sharedprefs from FlutterSecureStorage.

    If you need to enable the android:fullBackupContent for your app. Set up a backup rule to exclude the prefs used by the plugin:

    <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>

    Please check flutter_secure_storage for more details.

flutter_web_auth

flutter_web_auth is used behind Logto's flutter SDK. We rely on its webview-based interaction interface to open Logto's authorization pages.

note

This plugin uses ASWebAuthenticationSession on iOS 12+ and macOS 10.15+, SFAuthenticationSession on iOS 11, Chrome Custom Tabs on Android and opens a new window on Web. You can build it with iOS 8+, but it is currently only supported by iOS 11 or higher.

Register the callback url on Android

In order to capture the callback url from Logto's sign-in web page, you will need to register your sign-in redirectUri to the 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>