🫥

Serverpodの認証周りを調べてみた

2023/12/23に公開

はじめに

Serverpodを軽く触ってみました。
Serverpodの特徴として、認証周りの機能もついているので触ってみます。

標準で組み込まれている認証の種類

メールアドレス&パスワード
Googleアカウント連携
Appleアカウント連携

今回は「メールアドレス&パスワード」をやってみようと思います。

基本的には以下の記事を参考にしていきます。
https://medium.com/serverpod/getting-started-with-serverpod-authentication-part-1-72c25280e6e9

Get started

1. Project作成と準備

※ 前提としてserverpodはインストール済み、Dockerを起動しておく。

以下のコマンドでServerpodのプロジェクトを作成。
(mypodは任意の名前)以降、mypodという名称を付けたという前提で書いていきます。

serverpod create mypod

Dockerのセットアップをする。

cd mypod/mypod_server
docker-compose up --build --detach

authパッケージを取得する
mypod_servermypod_client内にあるpubspec.yamlserverpod_auth_xxxx: ^1.1.xを追加する
バージョンはserverpodのバージョンと合わせています。

dependencies:
  serverpod: ^1.1.1
  serverpod_auth_server: ^1.1.1
dependencies:
  serverpod_client: ^1.1.1
  serverpod_auth_client: ^1.1.1

生成設定ファイルにauth用のセットを追加する。
config/generator.yamlに以下を追加。

modules:
  serverpod_auth:
    nickname: auth

generator.yamlに追加したことで、コマンドを実行すると必要なファイルが自動生成されます。

dart pub get
serverpod generate

次にDBまわりの用意をしていきます。

mypod_serverにあるgeneratedtables-serverpod-auth.pgsqlを新規ファイルとして作成。
こちらのファイルのファイル全体をコピーします。
(参考にしたGet Startedの記事内にあるマイグレーションファイルのリンクが切れているため、それっぽい別の場所にあるマイグレーションファイルを使っています。)

docker psを実行し、mypod_server-postgres-1があることを確認します。

その後、terminal上でgeneratedフォルダに移動した後にDBを反映させるDBコマンドを実行します。

cd generated
docker cp ./tables-serverpod-auth.pgsql mypod_server-postgres-1:/docker-entrypoint-initdb.d/tables-serverpod-auth.pgsql
docker exec -u postgres mypod_server-postgres-1 psql mypod postgres -f /docker-entrypoint-initdb.d/tables-serverpod-auth.pgsql

ただ、👆をすると自分の環境だと途中からエラーになり、sqlファイルが最後まで実行できなかった。
(一回でもサーバーを起動するとデフォルトのsqlファイルのデータがDBに反映されてしまって、既にtableが存在しているというエラー?🤔)

BEGIN
CREATE TABLE
CREATE INDEX
...(略
CREATE INDEX
CREATE INDEX
psql:/docker-entrypoint-initdb.d/tables-serverpod-auth.pgsql:110: ERROR:  relation "serverpod_auth_key" already exists
psql:/docker-entrypoint-initdb.d/tables-serverpod-auth.pgsql:113: ERROR:  current transaction is aborted, commands ignored until end of transaction block

とりあえずは一度mypodに関するテーブルを全て削除し、もう一度上記のコマンドを実行すると成功するようになりました。

次にDBViewer的なアプリを利用してDBの内部を見てみます。
接続情報はdevelopment.yamlpasswords.yamlにあるので、必要なデータを入力していきます。
以下のテーブルが存在していればOKかと思います。

これで下準備は完了で、次にクライアント(Flutter)側のセットアップをしていきます。

2. Flutterのセットアップ

mypod_flutterpubspec.yamlに二つのパッケージを追加します。
その後、flutter pub getを実行しておきます。

  serverpod_flutter: ^1.1.1
  serverpod_auth_email_flutter: ^1.1.1 
  serverpod_auth_shared_flutter: ^1.1.1

とりあえずこの時点でサーバーの起動および、Flutterアプリからのアクセスができているかを確認してみます。

サーバー側の起動

cd mypod_server
dart bin/main.dart

Flutterアプリの起動(open -a SimulatorでiOS Simulatorを起動してから、Flutterアプリを起動します)

open -a Simulator
cd mypod_flutter
flutter run

以下の画面で「Send to Server」をタップした時に「Enter your name」に入力した文字が「Hello [入力した文字]」と表示されれば、サーバーとの接続が正常にできています。

(サーバーとの接続が失敗すると、SocketException等何かしらのエラーが表示され、「No Server response yet.」と表示している部分が赤色に変わります。)

サーバーとの接続が確認できたところで、Flutterアプリ上からログインやサインインをできるよう実装していきます。

2.1 認証のセットアップ

mypod_flutter/lib/main.dartMyHomePageを以下のように修正します。
SignInWithEmailButtonserverpod_auth_email_flutterのパッケージが提供しているWidgetなので、とりあえず以下丸コピで動作できるかと思います。

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(
        child: SignInWithEmailButton(
          caller: client.modules.auth,
        ),
      ),
    );
  }
}

以下の画面が表示されると思います。

UI側の設定が終わったので、SessionManagerというものをセットアップしていきます。

main.dartの一番上の方にあるClientauthenticationKeyManagerを追加します。

var client = Client(
  'http://localhost:8080/',
)..connectivityMonitor = FlutterConnectivityMonitor();

var client = Client(
  'http://localhost:8080/',
  authenticationKeyManager: FlutterAuthenticationKeyManager(),
)..connectivityMonitor = FlutterConnectivityMonitor();

SessionManagervar clientの下に宣言して、main関数で初期化します。

var client = Client(
  'http://localhost:8080/',
  authenticationKeyManager: FlutterAuthenticationKeyManager(),
)..connectivityMonitor = FlutterConnectivityMonitor();

late SessionManager sessionManager;

void main() async {
  sessionManager = SessionManager(caller: client.modules.auth);
  await sessionManager.initialize();
  runApp(const MyApp());
}

次にサーバー側の設定をしていきます。
mypod_serverlib/server.dartrun関数に次のコードを追加します。

  final pod = Serverpod(
    args,
    Protocol(),
    Endpoints(),
  );
  
  // auth[ここを追加します。]
  auth.AuthConfig.set(auth.AuthConfig(
    sendValidationEmail: (session, email, validationCode) {
      print('Validation code: $validationCode');
      return Future.value(true);
    },
    sendPasswordResetEmail: (session, userInfo, validationCode) {
      print('Validation code: $validationCode');
      return Future.value(true);
    },
  ));
  
  ...

今、追加したコードでアカウント登録時やパスワードリセットの際にメール等に認証番号をprintで確認できるようになります。

コード的には準備完了なので、アカウントを作成していきます。

2.2 アカウントの準備

サーバーを起動して、Flutterアプリも起動していきます。
サーバー起動

cd mypod_server
dart bin/main.dart

Flutterアプリ起動

cd mypod_flutter
flutter run

Flutterアプリ上のSign in with Emailというボタンをタップ。
以下のようなポップアップが表示されるのでそれぞれの項目を入力して「Create Account」をタップします。

「Create Account」をタップすると、認証番号を入力する画面が表示されます。
そうすると、サーバーを動作しているコンソールに以下のようにログ出力されるのでこの番号をアプリに入力します。

Validation code: 62529545

認証コードを入力して、「Sign In」をタップするとアカウント作成完了です。

DBViewerのツールで確認してみます。
serverpod_user_infoのテーブルに以下のように入力した内容のデータが登録できていれば、アカウント登録完了です。

試しに、作成したアカウントでログインをしてみます。
手順としては以下になります。

  1. Flutterアプリで「Sign in with Email」をタップ
  2. 「I have an account」をタップ
  3. メールアドレスとパスワードを入力して「Sign In」をタップ

先ほど登録したメールアドレスをパスワードでログインをすると通常にログインできます。
また、それ以外の組み合わせのメールアドレス、パスワードを入力すると「Incorrect password」と表示され、エラーになります。

Flutterアプリにてログインしているアカウントの情報はSessionManagerでみることができます。

    print(sessionManager.isSignedIn);
    print(sessionManager.signedInUser);
    
    flutter: true
    flutter: {"id":1,"userIdentifier":"example@example.com","userName":"user","fullName":null,"email":"example@example.com","created":"2023-12-07T21:23:03.927547Z","imageUrl":"http://localhost:8080/serverpod_cloud_storage?method=file&path=serverpod%2Fuser_images%2F1-1.jpg","scopeNames":[],"blocked":false}

printした情報を見るに既存のままだとユーザーを識別するIDにおいてはデフォルトの設定では存在しないようなので、また別途アカウント情報を作成する必要があるかもでした。
userIdentifierのパラメータがUUIDなどになればいいのですが、、、

また、ログアウトをするにはSessionManagerのsignOutで実行できます。

            TextButton(
              onPressed: () {
                sessionManager.signOut();
              },
              child: const Text('Logout'),
            ),

アカウントの連携をするだけであればFirebaseの方が利用しやすかったりする気が若干しますが、Serverpodも簡単に認証だったりの実装ができました。

Discussion