Flutter auth with Appwrite 1/4


A bit of context

In this article, we will focus on email/password authentication

A schema

The code

The contract

Our User model can be as simple as this for now

class User {
  final String id;

  const User({required this.id});
}

Following our schema, we need a UserRepository that will be used by our controllers to authenticate the user and deal with our service (here Appwrite).

Here is what a basic UserRepository can look

/// [UserRepository] handles user authentication
/// and account creation
abstract class UserRepository {
  /// Get the currently authenticated user
  /// or null if not authenticated
  Future<User?> authenticatedUser();

  /// Create an account with email and password
  /// and return the user
  Future<User> createAccountWithEmailAndPassword({
    required String email,
    required String password,
  });

  /// Sign in with email and password
  /// and return the user
  Future<User> loginWithEmailAndPassword({
    required String email,
    required String password,
  });
}

The implementation

As we are using Riverpod we need a provider for our AppWrite Client. But this does not change the main content of what we are doing here, it is just a way to do dependency injection.

I’m not using any local instance of Appwrite here, so I hit the production with https://cloud.appwrite.io/v1

final appWriteProvider = Provider((ref) {
  return new Client()
    ..setEndpoint('https://cloud.appwrite.io/v1')
    /// Your project ID
    ..setProject('XXXXXXXXX')
    ..setSelfSigned(status: kDebugMode);
});

Now, we need our AppwriteUserRepository.

/// [AppwriteUserRepository] implements [UserRepository]
/// by using the Appwrite SDK
class AppwriteUserRepository implements UserRepository {
  final Account account;

  AppwriteUserRepository({
    required Client client,
  }) : account = Account(client);

}

So, we inject our client and use the Account part of it.

First thing first, we need to convert Appwrite’s, User class to our class.

This is not mandatory, but helps if one day we want to switch our auth provider

/// Prefix imports of appwrite models with an `a`
import 'package:appwrite/models.dart' as a;

/// Converts an appwrite [a.User] to a [User]
User _convertAppwriteUser(a.User u) {
  return User(
    id: u.$id,
  );
}

Get the current authenticated user

@override
Future<User?> authenticatedUser() async {
  try {
    final currentUser = await account.get();
    return _convertAppwriteUser(currentUser);
  } catch (e) {
    return null;
  }
}

Be aware that, under the hood, this method will do an http call to /account, so it would be nice to cache the result for some time.

Create an account using email and password

@override
Future<User> createAccountWithEmailAndPassword(
  String email,
  String password,
) async {
  return account
      .create(
        userId: const Uuid().v4(),
        email: email,
        password: password,
      )
      .then(_convertAppwriteUser);
}

As you can see, create takes a userId parameter, it requires a unique identifier. For our purpose, we will use uuid package and generate a v4 uuid to use.

Login using email and password

@override
Future<User> loginWithEmailAndPassword(String email, String password) async {
  return account
      .createEmailPasswordSession(email: email, password: password)
      .then((s) => account.get())
      .then(_convertAppwriteUser);
}

To log in, we will use the createEmailPasswordSession method that creates a Session instance.

The Session does contain the userId so we could have write this instead

return account
      .createEmailPasswordSession(email: email, password: password)
      .then((s) => User(id: s.userId))

But, as you might need other info from the user in a second time (email, name, status), it is better to fetch the User instead using account.get()

Takeaway

Now using this code you are able to do a basic auth in your Flutter app using Appwrite. In the next blog post, we will learn how to use social connect for Google and Apple