DEV Community

Palomino for Logto

Posted on • Originally published at blog.logto.io

Custom FlutterFlow authentication using Logto

Learn how to implement custom authentication in your Flutter application using Logto Flutter SDK.


Introduction

FlutterFlow is a low-code platform that allows you to build Flutter applications visually. It provides a drag-and-drop interface to design your app's UI and generates the corresponding Flutter code. According to the official documentation, it provides three different authentication integration options:

  1. Through the build-in Firebase authentication
  2. Through the build-in Supabase authentication
  3. Custom authentication

For the first two, FlutterFlow provides a seamless integration with Firebase and Supabase. You will need to set up your Firebase or Supabase project and configure the authentication settings in FlutterFlow. However, if you want to use a different authentication provider, you will need to implement the authentication logic yourself.

As for the custom authentication, FlutterFlow provides a way to integrate with any authentication provider relying on a single custom authentication API.

However, a direct user credential exchange between the client and the authentication server is not recommended by modern security standards. Instead, you should use a secure authentication flow such as OAuth 2.0 or OpenID Connect (OIDC) to authenticate users. For modern OAuth 2.0 or OIDC based Identity Providers (IdP) such as Auth0, Okta, and Logto, a resource owner password credentials (ROPC) grant type is not recommended or prohibited due to security reasons. See Deprecated ropc grant type for more details.

A standard OAuth 2.0 or OIDC authentication flow involves multiple steps and redirects between the client application, the authorization server, and the user's browser. In this post, we will show you how to customize the FluterFlow's CustomAuthManager class using Logto Flutter SDK to implement a secure authentication flow in your FlutterFlow application.

Prerequisites

  • A Logto Cloud account or a self-hosted Logto instance. (Checkout the ⚑ Get started guide to create a Logto instance)
  • A Flutter application created using FlutterFlow.
  • Register a flutter application in your Logto console.
  • A GitHub repository to manage your custom code in FlutterFlow.
  • Checkout our Flutter SDK's integration guide.
  • Logto Flutter SDK package is available on pub.dev and Logto github repository.
  • The SDK is currently only suitable for Android and iOS platforms.
  • Manage custom code in GitHub is a premium feature in FlutterFlow. You need to upgrade your FlutterFlow to Pro plan to enable this feature.

Step 1: Enable manage custom code in FlutterFlow

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 link and sync your FlutterFlow project with GitHub.

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 UI editor to your codebase.
  3. develop: The branch where you can modify your custom code.

Step 2: Design and create your custom UI flow in FlutterFlow

Build your pages

Create your UI in FlutterFlow. You can follow the FlutterFlow documentation to create your UI based on your requirements. In this tutorial, for the minimum requirement, we will assume you have two pages:

  1. A simple HomePage with a sign-in button. (No sign-in form is needed, user authentication flow is handled at the Logto side. Please check customize sie guide for more details.)
    Image description

  2. A user profile page to display user information and sign-out button.
    Image description

Enable custom authentication in FlutterFlow

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

Step 3: Sync your FlutterFlow project with GitHub

After you have created your custom UI and enabled the custom authentication in FlutterFlow, you need to sync your project with GitHub. Go to the integrations - GitHub page and and click on the Push to Repository
Image description

Step 4: Customize the CustomAuthManager code

Switch to the develop branch in your GitHub repository and merge the latest changes from the flutterflow branch. This will sync all the UI changes to your develop branch including your page widgets, and the pre built CustomAuthManager class.

Install Logto SDK dependency

Add the Logto SDK dependency to your project.

  flutter pub add logto_dart_sdk
Enter fullscreen mode Exit fullscreen mode

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 class

UserProvider class is responsible for managing the user authentication state. We need to customize the properties to store the user authentication information provided by the Logto SDK.

Add a idToken property with type OpenIdClaims to store the id_token claims for the authenticated user.

OpenIdClaims class is defined in Logto SDK, which will provide the OIDC standard id_token claims from a 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);
Enter fullscreen mode Exit fullscreen mode

Customize the CustomAuthManager class and initialize Logto client

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

// Logto configuration
  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;
Enter fullscreen mode Exit fullscreen mode

The initialize method will init a Logto client instance 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 data securely. Once the user is authenticated, the id_token claims will be stored in the local storage.

Implement the sign-in method using Logto client

Call the LogtoClient.signIn method will initiate a standard OIDC authentication flow. The Logto sign-in page will be opened in a webview. The webview based authentication flow is powered by 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,
    );
  }
Enter fullscreen mode Exit fullscreen mode

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),
    );
  }
Enter fullscreen mode Exit fullscreen mode

The signOut method will clear the user authentication data stored in the local storage and update the current user stream.

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;
Enter fullscreen mode Exit fullscreen mode

Step 5: Update the sign-in and sign-out buttons 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 implement sign-in 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',
  // ...
)
Enter fullscreen mode Exit fullscreen mode

User will be redirected to the user page after successful authentication.

User Profile Page

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

For example, to display the user information using multiple Text widgets:

// 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}',
  ),
]
Enter fullscreen mode Exit fullscreen mode

Trigger the sign-out method when the user clicks on the sign-out button and redirect the user back to the home page.

// lib/pages/user/user_widget.dart

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

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

Enter fullscreen mode Exit fullscreen mode

Testing

Run your FlutterFlow application on an emulator. Click on the sign-in button on the home page to initiate the authentication flow. The Logto sign-in page will be opened in a webview. After successful authentication, the user will be redirected to the user profile page. The user information will be displayed on the user profile page. Click on the sign-out button to sign out the user and redirect the user back to the home page.

Don't forget to merge the develop branch back to the main branch and push the changes to the GitHub repository.

FlutterFlow github push is a one-way sync from FlutterFlow to GitHub. Any changes made under the fluterflow branch will be overwritten by the FlutterFlow UI editor when you push the changes to the GitHub repository. Make sure to use the develop branch to manage your custom code and merge the changes back to the main branch for deployment.

Further Readings

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

Top comments (0)