Skip to main content

Command Palette

Search for a command to run...

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

How to integrate Serverpod Authentication, giving users the ability to sign up, sign in, and reset their password with a validation code.

Published
5 min read
Full-Stack Mobile Development (Flutter + Serverpod) #3 - Serverpod Authentication
S

Hello, I'm Samuel, also known as Tech With Sam.

I am passionate about learning and teaching programming, particularly Flutter and Dart at the moment. Please support me by subscribing to my newsletter. Thanks!

Subscribe for weekly tutorials and tips, or DM me to bring your app idea to life.

Questions? Join me on Discord: https://discord.gg/8X7dPYujqm For Business: techwithsam10@gmail.com

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! 🥰👨‍💻

Full-Stack Mobile Development with Flutter & Serverpod

Part 1 of 3

In this new series, we’re building a real-world fintech to-do app from scratch: secure tasks, real-time updates, and cloud-ready deploys. No more half-solutions!

Up next

Full-Stack Mobile Development (Flutter + Serverpod) #2 — Serverpod Installation and Setup

How to set up and install serverpod to build your first serverpod project.