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:
-
A Supabase account (free tier is sufficient)
-
Basic knowledge of Dart and Flutter
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.
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.
Choose your Flutter SDK path, then click the Next button.
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.
Here's what the Flutter Supabase app looks like:
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.
On a successful login, you will be redirected to this screen.
You will see the registered users in the Supabase dashboard -> 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:
- Learn Flutter From Scratch
- Flutter, Beginner to Intermediate
- Flutter in 7 Days
- Learn Flutter from scratch
- Dart and Flutter: The Complete Developer's Guide
Thanks!