Eshiet Ekemini A graduate of University of Uyo and a tech enthusiast, Ekemini has been building for mobile for two years, with a particular focus on Kotlin and Flutter.

Using Supabase for authentication in Flutter

6 min read 1841

Using Supabase For Authentication In Flutter

In most of our applications, we authenticate our users to keep track of their activities and provide them with personalized experiences. There are lots of ways to achieve this, especially in Flutter. Some of the popular ways include adding authentication to our app by employing Firebase or building our own custom API and integrating it in our applications to deal with user authentication.

However, those options may not be the best for your use case. This article will walk you through an introduction to the capabilities of Supabase, a Firebase alternative, and how to add authentication to your Flutter app using Supabase.

What is Supabase?

Supabase is an open source alternative to Firebase.

Firebase is a Backend-as-a-Service (BaaS) by Google that enables developers to create iOS, Android, and web apps with out-of-the-box support to integrate tools for tracking analytics, reporting and fixing app crashes, authentication, storage, and NoSQL databases.

Even though Firebase has plenty of features, Supabase is more robust and can run on various platforms. Its portability makes it ideal for developers who are looking for a simple and flexible way to integrate third-party apps.

Supabase offers a straightforward API for incorporating third-party authentication services like Google, Apple, Twitter, Facebook, GitHub, Azure, GitLab, and Bitbucket. It also supports SAML-based enterprise logins.

One of the core differences between Supabase and Firebase is that while Firebase is not open source, Supabase is also free to use at the moment, and does not have any paid plan. Be mindful, the only major features that are currently active are that of database, authentication, and storage; other features such as cloud functions are still in development.

Getting started

In today’s article, we would be exploring how authentication works with Supabase.

In order to use Supabase, you need to create a new project by signing in. Once logged in, create a new project by clicking the button as shown in the image below.

Create A New Project



You’ll be prompted with a window to enter the name of your organization, and after that, you’ll proceed to the new screen shown below.

Proceed To Next Screen

Once you are done with filling out the basic details and your project has been set up on the Supabase dashboard, there are two important things required for connecting to our Supabase backend from our Flutter application. Those are:

-a Supabase URL 
-and a Supabase public API key

To get these, just click on the Settings tab.

Click On Settings Tab

Then select API and you will have your URL, and API key, displayed. You can now copy these and save them in your application.

Select API

Supabase by default enables email verification, which means that when using Supabase authentication with this setting turned on, your users are required to verify their email addresses in order for their accounts to become active.

For simplicity and since this is just a tutorial, we are going to disable email verification. Feel free to use and explore other options at your own leisure.

Go to the Settings tab on your Supabase dashboard and click on Auth Settings, then click on Settings under the Configuration tab.

There you can toggle off Enable email confirmations.

Toggle Off Enable Email Confirmations

Create a new Flutter project

Here we are going to create a simple Flutter project. We are going to have three widgets: for creating an account, logging in existing users, and a simple home screen that tells you if you are logged in or not.


More great articles from LogRocket:


Create a Supabase service class

But before that, let’s create a Supabase manager class to handle all of our initializations and authentication functions:

import 'package:flutter/material.dart';
import 'package:supabase/supabase.dart';
import 'package:supabase_authentication/core/toast.dart';

const String supabaseUrl = "your supabase url goes here ";
const String token =
    "your supabase token goes here";

class SupabaseManager {
  final client = SupabaseClient(supabaseUrl, token);


  Future<void> signUpUser(context, {String? email, String? password}) async {
    debugPrint("email:$email password:$password");
    final result = await client.auth.signUp(email!, password!);

    debugPrint(result.data!.toJson().toString());

    if (result.data != null) {
      showToastMessage('Registration Success', isError: false);
      Navigator.pushReplacementNamed(context, 'login');
      showToastMessage('Success', isError: false);
    } else if (result.error?.message != null) {
      showToastMessage('Error:${result.error!.message.toString()}',
          isError: true);
    }
  }

  Future<void> signInUser(context, {String? email, String? password}) async {
    debugPrint("email:$email password:$password");
    final result = await client.auth.signIn(email: email!, password: password!);
    debugPrint(result.data!.toJson().toString());

    if (result.data != null) {
      showToastMessage('Login Success', isError: false);
      Navigator.pushReplacementNamed(context, '/home');
      showToastMessage('Success', isError: false);
    } else if (result.error?.message != null) {
      showToastMessage('Error:${result.error!.message.toString()}',
          isError: true);
    }
  }


  Future<void> logout (context)async{
    await client.auth.signOut();
    Navigator.pushReplacementNamed(context, 'login');
  }
}

In the file above, we have two variables to store our token and Supabase URL. We also create a class, and inside this class we have an object of SupabaseClient that is required to make all our API calls to Supabase. It instantiated using our URL and token.

Subsequently we have the signup, sign-in, and logout functions.

Both the login and signup functions take in an email and password as parameters then pass them to the login and signup methods on the SupabaseClient object. The result is a session object from the SupabaseClient class that we check for to know if our request was successful or not. Then, a message is displayed to the user.

When login is complete, the user is navigated to the home screen page, and for signup, the user is taken to the login page.

The logout functionality simply does as it says, using the SupabaseClient object to sign out the user and navigate back to our authentication screen.

Set up routing in our app

In our main activity, we have defined our routes in our Material app:

import 'package:flutter/material.dart';
import 'package:supabase_authentication/login.dart';
import 'package:supabase_authentication/signup.dart';

import 'homescreen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Supabase Demo',
      debugShowCheckedModeBanner: false,
      initialRoute: 'login',
      routes: {
        'login': (_) => const LoginPage(),
        '/signup': (_) => const SignUpPage(),
        '/home': (_) => const HomeScreen(),
      },
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const LoginPage(),
    );
  }
}

Create a user interface

For this next part, we are simply going to create the visual portion of our applications that basically includes our signup page and login page, as well the final home screen we would take the user to after successful authentication.

This is what our sign-up page looks like:

import 'package:flutter/material.dart';
import 'package:supabase_authentication/homescreen.dart';
import 'package:supabase_authentication/login.dart';
import 'package:supabase_authentication/supabase_utils.dart';

class SignUpPage extends StatefulWidget {
  const SignUpPage({Key? key}) : super(key: key);

  @override
  _SignUpPageState createState() => _SignUpPageState();
}

class _SignUpPageState extends State<SignUpPage> {
  final _supabaseClient = SupabaseManager();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        centerTitle: true,
        title: const Text("Sign Up Page"),
      ),
      body: SingleChildScrollView(
        child: Form(
          key: _formKey,
          child: Column(
            children: <Widget>[
              const SizedBox(
                height: 200,
              ),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 15),
                child: TextFormField(
                  controller: _emailController,
                  decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Email',
                      hintText: 'Enter a valid email'),
                  validator: (String? value) {
                    if (value!.isEmpty || !value.contains('@')) {
                      return 'Email is not valid';
                    }
                  },
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(
                    left: 15.0, right: 15.0, top: 15, bottom: 0),
                //padding: EdgeInsets.symmetric(horizontal: 15),
                child: TextFormField(
                  obscureText: true,
                  controller: _passwordController,
                  decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Password',
                      hintText: 'Enter secure password'),
                  validator: (String? value) {
                    if (value!.isEmpty) {
                      return 'Invalid password';
                    }
                  },
                ),
              ),
              const SizedBox(
                height: 20,
              ),
              Container(
                height: 50,
                width: 250,
                decoration: BoxDecoration(
                    color: Colors.blue,
                    borderRadius: BorderRadius.circular(20)),
                child: TextButton(
                  onPressed: () {
    if (_formKey.currentState!.validate()) {
                    _supabaseClient.signUpUser(context,
                        email: _emailController.text,
                        password: _passwordController.text);}
                  },
                  child: const Text(
                    'Sign Up',
                    style: TextStyle(color: Colors.white, fontSize: 25),
                  ),
                ),
              ),
              const SizedBox(
                height: 130,
              ),
              TextButton(
                  onPressed: () {
                      Navigator.push(context,
                          MaterialPageRoute(builder: (_) => const LoginPage()));

                  },
                  child: const Text('Already a User? Login'))
            ],
          ),
        ),
      ),
    );
  }
}

Our login widget is also similar:

import 'package:flutter/material.dart';
import 'package:supabase_authentication/signup.dart';
import 'package:supabase_authentication/supabase_utils.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);

  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _supabaseClient = SupabaseManager();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        centerTitle: true,
        title: const Text("Login Page"),
      ),
      body: SingleChildScrollView(
        child: Form(
          key: _formKey,
          child: Column(
            children: <Widget>[
              const SizedBox(
                height: 200,
              ),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 15),
                child: TextFormField(
                  controller: _emailController,
                  decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Email',
                      hintText: 'Enter a valid email'),
                  validator: (String? value) {
                    if (value!.isEmpty || !value.contains('@')) {
                      return 'Email is not valid';
                    }
                  },
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(
                    left: 15.0, right: 15.0, top: 15, bottom: 0),
                //padding: EdgeInsets.symmetric(horizontal: 15),
                child: TextFormField(
                  controller: _passwordController,
                  obscureText: true,
                  decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Password',
                      hintText: 'Enter secure password'),
                  validator: (String? value) {
                    if (value!.isEmpty) {
                      return 'Invalid password';
                    }
                  },
                ),
              ),
              TextButton(
                onPressed: () {},
                child: const Text(
                  'Forgot Password',
                  style: TextStyle(color: Colors.blue, fontSize: 15),
                ),
              ),
              Container(
                height: 50,
                width: 250,
                decoration: BoxDecoration(
                    color: Colors.blue,
                    borderRadius: BorderRadius.circular(20)),
                child: TextButton(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                    _supabaseClient.signInUser(context,
                        email: _emailController.text,
                        password: _passwordController.text);}
                  },
                  child: const Text(
                    'Login',
                    style: TextStyle(color: Colors.white, fontSize: 25),
                  ),
                ),
              ),
              const SizedBox(
                height: 130,
              ),
              TextButton(
                  onPressed: () {

                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (_) => const SignUpPage()));

                  },
                  child: const Text('New User? Create Account')),
              const SizedBox(
                height: 30,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

And finally, we have our home screen:

import 'package:flutter/material.dart';
import 'package:supabase_authentication/supabase_utils.dart';


class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final _supabaseClient = SupabaseManager();
  @override
  Widget build(BuildContext context) {
    return  Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(
        centerTitle: true,
        title: const Text("Home Page"),
          actions: <Widget>[
            PopupMenuButton<String>(
              onSelected: handleClick,
              itemBuilder: (BuildContext context) {
                return {'Logout',}.map((String choice) {
                  return PopupMenuItem<String>(
                    value: choice,
                    child: Text(choice),
                  );
                }).toList();
              },
            ),
          ],
    ),
    body:const SizedBox(
      height: double.infinity,
      child: Center(child: Text("You are successfully logged in"),)
    )

    );
  }

  void handleClick(String value) {
    switch (value) {
      case 'Logout':
_supabaseClient.logout(context);
        break;

    }
  }
}

Final thoughts

Supabase makes the process of adding a backend solution to your application seamless. Authentication is an essential feature in most applications, and with the use of Supabase, you can solve this easily without having to build your own custom solution from the ground up.

Also keep in mind that Supabase supports other forms of authentication besides the ones covered in this article. Social authentication providers like Google, Facebook, GitHub, and others are also supported by Supabase. For more information on this, simply check out the Supabase documentation to learn more.

If you found this post on how to build a signature capture application informative or have a question, drop a comment below.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Eshiet Ekemini A graduate of University of Uyo and a tech enthusiast, Ekemini has been building for mobile for two years, with a particular focus on Kotlin and Flutter.

Leave a Reply