Flutter Authentication with Supabase

by Didin J. on Jun 17, 2025 Flutter Authentication with Supabase

Learn how to implement email/password authentication in Flutter using Supabase with a clean UI, secure login, signup, and logout functionality.

In modern app development, user authentication is one of the most essential features. Flutter, Google’s powerful UI toolkit, allows developers to build natively compiled apps for mobile, web, and desktop from a single codebase. Pairing Flutter with Supabase, an open-source Firebase alternative, provides a simple and scalable backend for authentication and data management.

In this tutorial, you will learn how to implement email/password authentication in a Flutter app using Supabase. By the end of this guide, you’ll have a working login, signup, and logout flow.

Prerequisites

Before we begin, make sure you have the following:


Step 1: Create a Supabase Project

1. Go to https://supabase.com and sign in.

2. Click New Project.

3. Choose a project name, password, and region.

 Flutter Authentication with Supabase - create project

4. After the project is created, go to Settings > Authentication, and enable Email under External OAuth Providers.

5. Navigate to Project Overview > Project API and copy your URL and anon key. You will use these in your Flutter app.


Step 2: Create a New Flutter Project

Start Android Studio, then choose the "New Flutter Project" button.

Flutter Authentication with Supabase - new flutter project

Choose your Flutter SDK path, then click the Next button.

Flutter Authentication with Supabase - new project

Fill in all the required fields, then click the "Create" button. Now, the Flutter project is ready to develop.


Step 3: Add Required Dependencies

Open pubspec.yaml and add:

dependencies:
  flutter:
    sdk: flutter
  supabase_flutter: ^2.9.1

Then click the "Pub Get" button or run in the terminal:

flutter pub get


Step 4: Initialize Supabase

In lib/main.dart:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

import 'auth_gate.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Supabase.initialize(
    url: 'https://your-project.supabase.co',
    anonKey: 'your-anon-key',
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Supabase Auth',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
        inputDecorationTheme: const InputDecorationTheme(
          border: OutlineInputBorder(),
        ),
      ),
      home: const AuthGate(),
    );
  }
}


Step 5: Create the AuthGate

Create lib/auth_gate.dart:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'login_screen.dart';
import 'home_screen.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});

  @override
  Widget build(BuildContext context) {
    final session = Supabase.instance.client.auth.currentSession;
    return session == null ? const LoginScreen() : const HomeScreen();
  }
}


Step 6: Build Login and Signup Screens

login_screen.dart

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'home_screen.dart';
import 'signup_screen.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _supabase = Supabase.instance.client;

  Future<void> _login() async {
    final response = await _supabase.auth.signInWithPassword(
      email: _emailController.text,
      password: _passwordController.text,
    );
    if (response.user != null) {
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const HomeScreen()),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Login failed')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              decoration: const InputDecoration(labelText: 'Email'),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _passwordController,
              obscureText: true,
              decoration: const InputDecoration(labelText: 'Password'),
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _login,
                child: const Text('Login'),
              ),
            ),
            TextButton(
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const SignupScreen()),
              ),
              child: const Text('No account? Sign up'),
            ),
          ],
        ),
      ),
    );
  }
}

signup_screen.dart

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'login_screen.dart';

class SignupScreen extends StatefulWidget {
  const SignupScreen({super.key});

  @override
  State<SignupScreen> createState() => _SignupScreenState();
}

class _SignupScreenState extends State<SignupScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _supabase = Supabase.instance.client;

  Future<void> _signup() async {
    final response = await _supabase.auth.signUp(
      email: _emailController.text,
      password: _passwordController.text,
    );
    if (response.user != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Check your email to confirm your account')),
      );
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const LoginScreen()),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Signup failed')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sign Up')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              decoration: const InputDecoration(labelText: 'Email'),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _passwordController,
              obscureText: true,
              decoration: const InputDecoration(labelText: 'Password'),
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _signup,
                child: const Text('Sign Up'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}


Step 7: Home Screen and Logout

home_screen.dart

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'login_screen.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  void _logout(BuildContext context) async {
    await Supabase.instance.client.auth.signOut();
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(builder: (context) => const LoginScreen()),
    );
  }

  @override
  Widget build(BuildContext context) {
    final user = Supabase.instance.client.auth.currentUser;
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Welcome ${user?.email ?? 'User'}!',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () => _logout(context),
              child: const Text('Logout'),
            ),
          ],
        ),
      ),
    );
  }
}


Step 8: Run to The Device

Connect your Android or iOS device to your computer, then run this app by clicking the Play button.

Flutter Authentication with Supabase - run to device

Here's what the Flutter Supabase app looks like:

Flutter Authentication with Supabase - sign up

After clicking the Sign Up button, you will receive the verification email to your email. But, that's optional, you can continue to the login page.

Flutter Authentication with Supabase - login page

On a successful login, you will be redirected to this screen.

Flutter Authentication with Supabase - home screen

You will see the registered users in the Supabase dashboard -> Authentication -> Users:

Flutter Authentication with Supabase - authentication users


Conclusion

You’ve just implemented a basic email/password authentication flow using Flutter and Supabase with a polished UI. Supabase makes it easy to manage user sessions, while Flutter ensures a seamless and beautiful UI across platforms. From here, you can explore more features like third-party providers, row-level security, and database integration.

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about Flutter, Dart, or related, you can take the following cheap course:

Thanks!