DEV Community

Cover image for Full-Stack Mobile Development (Flutter + Serverpod) #3 - Serverpod Authentication
Samuel Adekunle
Samuel Adekunle

Posted on • Originally published at Medium

Full-Stack Mobile Development (Flutter + Serverpod) #3 - Serverpod Authentication

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

Run:

dart pub get
Enter fullscreen mode Exit fullscreen mode

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

3. (Optional) Nickname the Module

For cleaner imports, add to fintech_todo_server/config/generator.yaml:

modules:
  serverpod_auth:
    nickname: auth
Enter fullscreen mode Exit fullscreen mode

4. Generate Code & Init DB Tables

Regen stubs:

cd fintech_todo_server
serverpod generate
Enter fullscreen mode Exit fullscreen mode

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

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

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

Run:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

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

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

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

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)