Hey, Flutter devs! It's TechWithSam again. Today, I will walk you through how to integrate Serverpod Authentication, giving users the ability to sign up, sign in, and reset their password with a validation code.
By episode's end, you'll have JWT-powered sessions flowing, with Flutter's SessionManager handling the heavy lifting - no manual token juggling. We're keeping it MVP-simple: focus on email authentication for secure trade tasks, and scopes for future admin features (e.g., user vs. trader roles).
Server-Side Setup: Wire Auth into Your Backend
Serverpod's serverpod_auth module auto-handles hashing, validation, and JWTs - perfect for fintech security without custom crypto nightmares.
1. Add the Auth Module
In fintech_todo_server/pubspec.yaml:
dependencies:
serverpod_auth_server: ^2.9.1 # Matches your Serverpod version
Run:
dart pub get
2. Hook the Authentication Handler
Update fintech_todo_server/bin/server.dart to route auth calls:
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_server/serverpod_auth_server.dart' as auth;
void run(List<String> args) async {
var pod = Serverpod(
args,
Protocol(), // Your protocol
Endpoints(), // Your endpoints
authenticationHandler: auth.authenticationHandler, // <-- Key line: Routes /auth/*
);
await pod.start();
}
3. (Optional) Nickname the Module
For cleaner imports, add to fintech_todo_server/config/generator.yaml:
modules:
serverpod_auth:
nickname: auth
4. Generate Code & Init DB Tables
Regen stubs:
cd fintech_todo_server
serverpod generate
Create/apply migration for auth tables (users, keys, etc.):
serverpod create-migration
docker compose up -d # If not running
dart run bin/main.dart --role maintenance --apply-migrations
5. Configure Auth Settings
In server.dart, set policies (fintech-tight: Long passwords, quick resets):
import 'package:serverpod_auth_server/module.dart' as auth;
void run(List<String> args) async {
// ... pod setup ...
auth.AuthConfig.set(
auth.AuthConfig(
minPasswordLength: 12, // Beefy for trades
emailSignInFailureResetTime: Duration(minutes: 5), // Lockout reset
maxAllowedEmailSignInAttempts: 5, // Anti-brute-force
passwordResetExpirationTime: Duration(hours: 24),
// Hooks: Optional for email sending (use Firebase/SES later)
sendValidationEmail: (email, code) => print('Send to $email: $code'), // Placeholder
sendPasswordResetEmail: (email, resetCode) => print('Reset for $email: $resetCode'),
// Fintech: Disable name edits for compliance
userCanEditFullName: false,
),
);
await pod.start();
}
Config - Why for Fintech MVP? - Default Fallback
minPasswordLength - Blocks weak creds - 8
maxAllowedEmailSignInAttempts- Stops spam logins - 5
sendValidationEmail- Custom emailer (e.g., via API in prod) - None (console log)
Best practice: Never commit secrets - use env vars for prod OAuth.
Client-Side Setup: Flutter Auth Flow
Now, wire Flutter's SessionManager for our workflow: Check sign-in → Show Login or Home.
1. Add Client Dependencies
In fintech_todo_flutter/pubspec.yaml:
dependencies:
flutter:
sdk: flutter
serverpod_flutter: ^2.5.0
serverpod_auth_client: ^2.5.0
serverpod_auth_shared_flutter: ^2.5.0
fintech_todo_client: # Your generated client
path: ../fintech_todo_client
flutter_secure_storage: ^9.0.0 # For JWT storage
Run:
flutter pub get
2. Init Client & SessionManager
In lib/main.dart (MVP: Localhost for dev; swap for device IP):
import 'package:flutter/material.dart';
import 'package:serverpod_flutter/serverpod_flutter.dart';
import 'package:serverpod_auth_client/serverpod_auth_client.dart';
import 'package:fintech_todo_client/fintech_todo_client.dart' as clientLib;
late SessionManager sessionManager;
late clientLib.Client spClient;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
const ipAddress = 'localhost'; // '10.0.2.2' for Android emulator
spClient = clientLib.Client(
'http://$ipAddress:8080/',
authenticationKeyManager: FlutterAuthenticationKeyManager(),
)..connectivityMonitor = FlutterConnectivityMonitor();
sessionManager = SessionManager(caller: spClient.modules.auth);
await sessionManager.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fintech Todo',
home: const AuthWrapper(), // Routes based on sign-in
debugShowCheckedModeBanner: false,
);
}
}
3. Build Auth Screens & Workflow
Core: AuthWrapper checks the sessionManager.isSignedIn → Login or Home.
Login/Register Screen (lib/screens/login_screen.dart):
import 'package:flutter/material.dart';
import 'package:serverpod_auth_client/serverpod_auth_client.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();
bool _isLogin = true; // Toggle for register
Future<void> _authAction() async {
try {
if (_isLogin) {
await sessionManager.signIn(email: _emailController.text, password: _passwordController.text);
} else {
// Register: Validate first, then create
final validation = await sessionManager.validateCredentials(
email: _emailController.text,
password: _passwordController.text,
);
if (validation.isValid) {
await sessionManager.createUser(
email: _emailController.text,
password: _passwordController.text,
userName: _emailController.text.split('@')[0], // Simple username
);
await sessionManager.signIn(email: _emailController.text, password: _passwordController.text);
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Invalid email/password')));
}
}
if (mounted) Navigator.pushReplacementNamed(context, '/home');
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Auth failed: $e')));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_isLogin ? 'Login' : 'Register')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(controller: _emailController, decoration: const InputDecoration(labelText: 'Email')),
TextField(controller: _passwordController, obscureText: true, decoration: const InputDecoration(labelText: 'Password')),
ElevatedButton(onPressed: _authAction, child: Text(_isLogin ? 'Login' : 'Register')),
TextButton(onPressed: () => setState(() => _isLogin = !_isLogin), child: Text(_isLogin ? 'Need an account?' : 'Have one?')),
// Optional Google: await sessionManager.signInWithOAuth(Provider.google);
],
),
),
);
}
}
Home Screen with Auth Guard (lib/screens/home_screen.dart):
import 'package:flutter/material.dart';
class AuthWrapper extends StatefulWidget {
const AuthWrapper({super.key});
@override
State<AuthWrapper> createState() => _AuthWrapperState();
}
class _AuthWrapperState extends State<AuthWrapper> {
@override
void initState() {
super.initState();
sessionManager.addListener(_onAuthChange); // Listen for sign-in/out
}
void _onAuthChange() => setState(() {}); // Rebuild on auth events
@override
Widget build(BuildContext context) {
return sessionManager.isSignedIn
? HomeScreen(user: sessionManager.signedInUser!) // Secure dashboard
: const LoginScreen();
}
}
class HomeScreen extends StatelessWidget {
final UserInfo user;
const HomeScreen({super.key, required this.user});
Future<void> _logout() async {
await sessionManager.signOutDevice(); // Or signOutAllDevices() for global
// Nav back to login auto via listener
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Welcome, ${user.userName}!'), actions: [IconButton(onPressed: _logout, icon: const Icon(Icons.logout))]),
body: const Center(child: Text('Tasks list here-secure & ready!')), // Empty state for Part 4
floatingActionButton: FloatingActionButton(
onPressed: () {}, // Tease CRUD create
child: const Icon(Icons.add),
),
);
}
}
Wrapping Up: Auth Locked, CRUD Next
Boom - your fintech todo's guarded: Email logins flow to a secure Home, scopes ready for roles, all in pure Dart (no Firebase silos). This nails 80% of auth pains, letting us prototype trade tasks without leaks. Fork the repo here and test a login - share wins in comments!
I hope you are ready to learn something incredible. Press that follow button if you're not following me yet. Also, make sure to subscribe to the newsletter so you're notified when I publish a new article. Kindly press the clap button as many times as you want if you enjoy it, and feel free to ask a question.
Catch the Ep.3 on YouTube. Next: Part 4, Task CRUD Operations - building those secure endpoints. What's your auth horror story? Hit Discord: https://discord.gg/NytgTkyw3R.
Keep stacking secure in 2025 - see you on the dashboard! 🚀
Samuel Adekunle, Tech With Sam YouTube | Part 4 Teaser
Happy Building! 🥰👨💻
Top comments (0)