User Authentication in Flutter Apps with Serverpod: A Step-by-Step Guide
Tags: flutter, serverless, dart, backend
Published: 2023-05-23 20:33:36

> Most enterprise software needs a method of authenticating its users. Depending on the type of `User`, it could be a programmer trying to access a resource through an _Application Programming Interface_ (API), or an end-user directly performing a task through forms on their website or apps.


Serverpod is a backend framework that provides feature-rich authentication modules and methods for your app needs. It is suitable for building both simple and complex backends for Flutter apps. However, Serverpod can be complex to set up at first, and the folder structure may not be intuitive for beginners. This tutorial aims to help you get started with Serverpod and make the process as smooth as possible.



## What To Expect in the Tutorial

This tutorial provides an overview of `authentication` in Serverpod. It also demonstrates authentication with <mark>Email</mark> and how to connect the backend (Serverpod) setup with your frontend (Flutter app). You should be able to apply the knowledge to other authentication factors

This tutorial assumes some prior knowledge of Serverpod and Flutter. You should check [this](https://dev.to/mathiasgodwin/crud-operations-with-serverpod-a-step-by-step-guide-for-flutter-apps-925) tutorial on how to get started with Serverpod.



<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/84uf6yx504gr4mt0lyyw.png">
  <figcaption> Serverpod's email auth screen.</figcaption>
</figure>


---

## Getting Started

> _Serverpod comes with built-in user management and authentication. You can either build your custom authentication method or use the `serverpod_auth` module. The module makes it easy to authenticate with email or social sign-ins_ - From the [_documentation_](https://docs.serverpod.dev/concepts/authentication#:~:text=Version%3A%201.1.0-,Authentication,-Serverpod%20comes%20with).


You will need an understanding of `Modules` in Serverpod to be able to add the authentication method used in this tutorial.

You will be using the `serverpod_auth` module for this tutorial. 

Clone this simple [noteapp](https://github.com/mathiasgodwin/noteapp) project from GitHub. The cloned project has some setup that was covered in this [tutorial](https://dev.to/mathiasgodwin/crud-operations-with-serverpod-a-step-by-step-guide-for-flutter-apps-925). You will be working with the project.

You could skip the `Modules` explanation part below if you have prior knowledge of how it works without missing anything.

---

## What Are Modules in Serverpod

As stated in the documentation: 
> _Serverpod is built around the concept of modules. A Serverpod module is similar to a Dart package but contains both server and client code and Flutter widgets. A module contains its namespace for endpoints and methods to minimize module conflicts._

This means modules in Serverpod are like your flutter packages that are reusable and publishable to [pub](https://pub.dev).

Modules in Serverpod consist of two folders, the `Client` and the `Server` folder.

Execute the command below to generate a module called `user_module` to see its structure:

```bash
serverpod create --template module user_module
```

You will get this folder structure:

```bash
├── user_module
│   ├── user_module_client
│   └── user_module_server
```

### How To Set up a Module

There are four (4) steps needed to fully add an existing module to your project.

Let's go through the steps with a demonstration.

Assuming your project is called `app`:
```dart 
├── app
│   ├── app_client
│   ├── app_flutter
│   ├── app_server
```

If you want to add the `user_module`, follow the steps below:

**Step 1:**  Database Setup

Serverpod generates a set of database tables whenever you create a new `Server` or `Module` project with the framework. These tables must be added to your database source before Serverpod could function properly. 

Your database tables live in `{name}_server/generated/` of your Server folder. For the generated `user_module`, the database is in `user_module_server/generated/`.

Copy the content of `{name}_server/generated/tables.pgsql` from both your current project (`app`) and module (`user_module`) into your database client. Then, run the query to add the necessary tables. Do this one after the other


**Step 2:**  Server setup

Add the module's server (`user_moduble/user_module_server/`) to your project's server (`app/app_server/`) as a dependency.

```yaml
dependencies:
  user_module_server: ^1.x.x # Assuming it was hosted
```

Then open your project's `config/generator.yaml` file and 
add a name known as `nickname` for your module, primarily used for referencing the module from your app. 

```yaml
modules:
  user_module:
    nickname: user
```

You will reference the module with the nickname from your Flutter app like this:

```dart
// ...
final user = client.user;
```

Execute the command below inside your project's server folder (`app/app_server/`) to get the module and generate the necessary files.
```bash
dart pub get
```
Then...
```bash
serverpod generate
```

**Step 3:**  Client setup

Add the module's client as a dependency on your project's client.

```yaml
dependencies:
  user_module_client: ^1.x.x
```

Then execute the command below.

```bash
dart pub get
```

**Step 4:**  Flutter app setup

Finally, a module can have Flutter package(s). The Flutter packages are usually separated. 
An example is the `serverpod_auth_google_flutter` package for the `serverpod_auth` module.

Consider adding the flutter packages to your project's flutter app (`app/app_flutter/`) as a dependency.



The preceding information is enough to get you started with this tutorial but you can Check the [documentation](https://docs.serverpod.dev/concepts/modules) for more details.


---



## How Does Authentication Work in Serverpod?

You'll be using Serverpod's [auth](https://pub.dev/packages/serverpod_auth_server) module which handles basic user information such as user profile picture or names and how to retrieve/update the information.

Before we dive in into the explanation of the Serverpod auths module, Let's have a basic understanding of how authentication works in Serverpod and how it can help us build a better backend app.

-  **Route guard**
   
Serverpod provides the logic to restrict access to an endpoint. For instance, you can require the user to log-in before accessing the full features of the app or website.

For example:

```dart
class AppEndpoint extends Endpoint {
  // ... 
  @override
  bool get requireLogin => true;

}
```

Restrictions can also be scoped. 

Scoping restricts access to a resource to a specific type of user, such as an admin or any user (logged-in or not logged-in).

```dart
class SomeEndpoint extends Endpoint {
  /// ...
  /// This requires that the user is a `driver` to access 
  this endpoint
  @override
  Set<Scope> get requiredScopes => {Scope('driver')};
}
```

With `serverpod_auth_server`, You can update the scope of a user with the snippet below.

```dart
/// Elevate the user’s scope to tell Serverpod that this user is now a `driver`
await Users.updateUserScopes(session, userId, {Scope('driver')});

```
   
-  **User information**
   
Serverpod provides a handy `Session` object that stores information about the current user.

You can access the logged-in user with the help of the `Session` object as follow.

```dart
Future<void> doSomething(Session session) async {
  final isSignedIn = await session.auth.authenticatedUserId != null;
  if (isSignedIn) {
   //Do something here
    }
}
```
    
-  **Session management**
    
Serverpod also provides a `SessionManager` object that tracks the user's state. The `SessionManager` can be used to sign out a user.

```dart
late SessionManager sessionManager;
late Client client;

void main() async {
   // ...
  client = Client('http://localhost:8080/', authenticationKeyManager: 
  FlutterAuthenticationKeyManager(),
  )..connectivityMonitor = FlutterConnectivityMonitor();   
  // The session manager keeps track of the signed-in state of the user. You
  // can query it to see if the user is currently signed in and get information
  // about the user.
  sessionManager = SessionManager(caller: client.modules.auth,
);
  await sessionManager.initialize();
}
```


Now that you have the basic knowledge of how Serverpod handles `endpoint` restrictions, Let's integrate the authentication module into the cloned [noteapp](https://github.com/mathiasgodwin/noteapp) project.


You need to start docker and Serverpod to continue.


Start docker and execute the command below inside `noteapp/noteapp_server/` to start the necessary docker containers and to start Serverpod.

```bash
docker-compose up --build --detach
```
Then:

```bash
dart bin/main.dart
```

You should see output like this on your terminal if everything goes fine.

![Starting Severpod and docker container](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m54s6j2md53rc4xzrrrb.png)


## Configuring serverpod_auth Module

Follow the steps below to setup `serverpod_auth` for your project.

**Step 1:**   Database setup
   	
Add the database content of `serverpod_auth_server` to your project's database. Copy the database query generated for the `serverpod_auth` module from the source [here](https://github.com/serverpod/serverpod/blob/main/modules/serverpod_auth/serverpod_auth_server/generated/tables.pgsql) into your database client and run the query.  


**Step 2:**   Server setup
    
Add serverpod_auth_server to your project's server (`noteapp/noteapp_server/`) as a dependency.

```yaml
dependencies:
  serverpod_auth_server: ^1.x.x
```

Then open `config/generator.yaml` file and 
add a `nickname` for your module. 

```yaml
modules:
  serverpod_auth:
    nickname: auth
```

Execute the command below inside your server folder (`noteapp/noteapp_server/`) to get the module and generate the necessary files.
    
```bash
dart pub get
```
Then:

```bash
serverpod generate
```

**Step 3:**   Client setup

Add the modules' client as a dependency on your project's client. 

```yaml
dependencies:
  user_module_client: ^1.x.x
```

Then execute the command below.

```bash
dart pub get
```   
 
**Step 4:**   Flutter app setup
    	
Add the following packages to your project Flutter app (`noteapp/noteapp_flutter/`) as a dependency.   
```yaml  
dependencies:
  # ...
  serverpod_auth_shared_flutter: ^1.x.x
  serverpod_auth_email_flutter: ^1.x.x
```


Some of the tables that will appear in your database after the preceding setup are...

![After adding auth query to database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dgzk128fywbf0zhdz68p.png)



### Configuring Email Authentication

You will need a `Simple Mail Transfer Protocol` (SMTP) provider and a package that can send email from Serverpod to properly set up email sign-in in your project. 

One of the email sender packages is [mailer](https://pub.dev/packages/mailer). It's actively maintained and easy to set up. Check [this](https://suragch.medium.com/how-to-send-yourself-email-notifications-from-a-dart-server-a7c16a1900d6) tutorial on how to send email from a dart server by [Suragch](https://suragch.medium.com/?source=author_recirc-----a7c16a1900d6----0---------------------4f4d6107_0109_4fb6_b989_c8633f0fd370-------) for further information.

You can use [brevo](https://brevo.com) for the SMTP provision part - Other options are [Mailchimp](https://mailchimp.com/) and [Mailgun](https://www.mailgun.com/).

Follow the steps below to add email auth.

**Step 1:** Server setup

Considering you've set up [brevo](https://brevo.com) SMTP provider, Open `noteapp_server/lib/server.dart`, and copy and paste the email auth configuration below inside the file. 

```dart
void run(List<String> args) async {
  // Initialize Serverpod and connect it with your generated code.
  // ... other codes

  Future<bool> sendVerificationEmail({
    required String emailAddress,
    required String verificationCode,
  }) async {
    bool? isSent;
    
     // SMTP server details
    final smtp = "smtp-relay.sendinblue.com";
    final username = "email@domain.com";
    final password = "password";
    final port = 111;

    final smtpServer = SmtpServer(
      smtp,
      port: port,
      username: username,
      password: password,
    );

    final message = Message()
      ..recipients.add(emailAddress)
      ..from = Address(username, "Company\'s name")
      ..subject = "Verification code"
      ..text = "Hi, \n This is your verification code: $verificationCode.";
    try {
      await send(message, smtpServer);
      isSent = true;
    } on MailerException {
      isSent = false;
    }
    return isSent;
  }

  auth.AuthConfig.set(
    auth.AuthConfig(
      sendValidationEmail: (session, email, validationCode) async {
        final isSent = await sendVerificationEmail(
            emailAddress: email, verificationCode: validationCode);
        return isSent;
      },
      sendPasswordResetEmail: (session, userInfo, validationCode) async {
        // Add password reset email logic.

        ///  The function requires a boolean value to be returned
        /// if the email was sent or not. returning `true` for demonstration
        return Future.value(true);
      },
    ),
  );


// ... pod start code below
}
```

Now, restart your server.


**Step 2:** Flutter app setup

Create three (3) empty files named: `serverpod_client.dart`, `account_page.dart`, `note_page.dart`, `login_page.dart`.


- Replace the content in your `main.dart` with the code below.

```dart
import 'package:flutter/material.dart';
import 'package:noteapp_flutter/login_page.dart';
import 'package:noteapp_flutter/note_page.dart';
import 'package:noteapp_flutter/serverpod_client.dart';


void main() async {
  // Need to call this as SessionManager is using Flutter bindings before runApp
  // is called.
  WidgetsFlutterBinding.ensureInitialized();
  await initializeServerpodClient();

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Serverpod Demo',
      theme: ThemeData(
        useMaterial3: true,
        primarySwatch: Colors.yellow,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    // Make sure that we rebuild the page if signed in status changes.
    sessionManager.addListener(() {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: sessionManager.isSignedIn ? NotePage() : LoginPage(),
    );
  }
}
```
Copy and paste the codes below into it respective files.

- `serverpod_client.dart`
```dart
import 'package:noteapp_client/noteapp_client.dart';
import 'package:serverpod_auth_shared_flutter/serverpod_auth_shared_flutter.dart';
import 'package:serverpod_flutter/serverpod_flutter.dart';

late SessionManager sessionManager;
late Client client;

Future<void> initializeServerpodClient() async {
  // Sets up a singleton client object that can be used to talk to the server from
  // anywhere in our app. The client is generated from your server code.
  // The client is set up to connect to a Serverpod running on a local server on
  // the default port. You will need to modify this to connect to staging or
  // production servers.
  client = Client(
    'http://localhost:8080/',
    authenticationKeyManager: FlutterAuthenticationKeyManager(),
  )..connectivityMonitor = FlutterConnectivityMonitor();

  // The session manager keeps track of the signed-in state of the user. You
  // can query it to see if the user is currently signed in and get information
  // about the user.
  sessionManager = SessionManager(
    caller: client.modules.auth,
  );
}
```

- `login_page.dart`
```dart
import 'package:flutter/material.dart';
import 'package:noteapp_flutter/serverpod_client.dart';
import 'package:serverpod_auth_email_flutter/serverpod_auth_email_flutter.dart';


/// TODO: Finish the docs
/// LoginPage to...
class LoginPage extends StatelessWidget {
  /// Static named route for page
  static const String route = 'Login';

  /// Static method to return the widget as a PageRoute
  static Route go() => MaterialPageRoute<void>(builder: (_) => LoginPage());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Dialog(
          child: Container(
            width: 260,
            padding: const EdgeInsets.all(16),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const Icon(
                  Icons.security,
                  size: 200,
                ),
                SignInWithEmailButton(
                  caller: client.modules.auth,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
```
- `account_page.dart`
```dart
import 'package:flutter/material.dart';
import 'package:noteapp_flutter/serverpod_client.dart';
import 'package:serverpod_auth_shared_flutter/serverpod_auth_shared_flutter.dart';


/// TODO: Finish the docs
/// AccountPage to...
class AccountPage extends StatelessWidget {
  /// Static named route for page
  static const String route = 'Account';

  /// Static method to return the widget as a PageRoute
  static Route go() => MaterialPageRoute<void>(builder: (_) => AccountPage());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title:const Text("Account"),),
        body: ListView(
      children: [
        ListTile(
          contentPadding:
              const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
          leading: CircularUserImage(
            userInfo: sessionManager.signedInUser,
            size: 42,
          ),
          title: Text(sessionManager.signedInUser!.userName),
          subtitle: Text(sessionManager.signedInUser!.email ?? ''),
        ),
        Padding(
          padding: const EdgeInsets.all(16),
          child: ElevatedButton(
            onPressed: () {
              sessionManager.signOut();
            },
            child: const Text('Sign out'),
          ),
        ),
      ],
    ));
  }
}

```
- `note_page.dart`
```dart
import 'package:flutter/material.dart';
import 'package:noteapp_client/noteapp_client.dart';
import 'package:noteapp_flutter/account_page.dart';
import 'package:noteapp_flutter/serverpod_client.dart';

/// TODO: Finish the docs
/// NotePage to...
class NotePage extends StatefulWidget {
  /// Static named route for page
  static const String route = 'Note';

  /// Static method to return the widget as a PageRoute
  static Route go() => MaterialPageRoute<void>(builder: (_) => NotePage());

  @override
  State<NotePage> createState() => _NotePageState();
}

class _NotePageState extends State<NotePage> {
  final _textEditingController = TextEditingController();

  // Calls the `createNote` method of the `note` endpoint. Will set `_errorMessage` field,
  void _callNote() async {
    try {
      await client.note.createNote(Note(
        data: _textEditingController.text,
        date: DateTime.now(),
      ));
    } catch (e) {
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Take Note'), actions: [
        IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.of(context).push(AccountPage.go());
            })
      ]),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.only(bottom: 16.0),
              child: TextField(
                controller: _textEditingController,
                decoration: const InputDecoration(
                  hintText: 'Enter your Note',
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(bottom: 16.0),
              child: ElevatedButton(
                onPressed: _callNote,
                child: const Text('Send to Server'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

```

> _Note :warning::
  Due to known issues with setup, you might need to remove the `SMTP` configuration part from the server code in order to successfully execute the program on your machine_.

The `SMTP` server configuration will not work properly if you are not running the Flutter app on Android or iPhone.

You will have a hard time making the `SMTP` work even if you are running the Flutter app on Android or iPhone and your server is on localhost.

For demonstration purposes, update the `noteapp/noteapp_server/lib/server.dart` file.
 
```dart
import 'package:serverpod/serverpod.dart';

import 'package:noteapp_server/src/web/routes/root.dart';
import 'package:serverpod_auth_server/module.dart' as auth;

import 'src/generated/protocol.dart';
import 'src/generated/endpoints.dart';

// This is the starting point of your Serverpod server. In most cases, you will
// only need to make additions to this file if you add future calls,  are
// configuring Relic (Serverpod's web-server), or need custom setup work.

void run(List<String> args) async {
  // Initialize Serverpod and connect it with your generated code.
  final pod = Serverpod(
    args,
    Protocol(),
    Endpoints(),
  );

  // If you are using any future calls, they need to be registered here.
  // pod.registerFutureCall(ExampleFutureCall(), 'exampleFutureCall');

  // Setup a default page at the web root.
  pod.webServer.addRoute(RouteRoot(), '/');
  pod.webServer.addRoute(RouteRoot(), '/index.html');
  // Serve all files in the /static directory.
  pod.webServer.addRoute(
    RouteStaticDirectory(serverDirectory: 'static', basePath: '/'),
    '/*',
  );


  auth.AuthConfig.set(
    auth.AuthConfig(
      sendValidationEmail: (session, email, validationCode) async {
        print(validationCode);
        return Future.value(true);
      },
      sendPasswordResetEmail: (session, userInfo, validationCode) async {
        // Add password reset email logic.

        ///  The function requires a boolean value to be returned
        /// if the email was sent or not. returning `true` for demonstration
        return Future.value(true);
      },
    ),
  );

  // Start the server.
  await pod.start();
}

```

---


## Conclusion

Serverpod is a very versatile backend framework. It provides features that make it easy to implement user authentication for Flutter apps.

**Below are some features of authentication in Serverpod:**

* User registration and login
* Password reset
* Account verification
* Session management
* Route guard


<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmnss4h4yimqsf7ht96z.gif">
  <figcaption> Note app email authentication</figcaption>
</figure>


If you are having trouble with the code and configuration, you can switch to the `auth` branch of the cloned repository to see the code used in this tutorial.

```bash
git checkout auth
```

## Where To Go From Here?

You may want to consider adding other authentication methods, such as Sign in with  [Google](https://docs.serverpod.dev/concepts/authentication#setting-up-sign-in-with-google) or building a custom authentication module.


## Reference
* Serverpod's [documentation](https://docs.serverpod.dev/)


## Credits
* Header<a href="https://www.freepik.com/free-photo/3d-illustration-computer-monitor-login-screen_34450515.htm#query=3d%20authenticate&position=14&from_view=search&track=ais"> image by upklyak</a> on Freepik

---