AstroでLuciaを利用して認証設定(username+password)

Astroをサポートしている認証ライブラリをあまり見かけることがありませんがSessionベース認証のLuciaはAstroにも対応しています。本文書ではusername+password認証に対応したLuciaを利用して動作確認を行います。LuciaはSessionベースの認証ライブラリでデータベースにSessionとCookiesにSessionIdを保存して認証を管理します。データベースにはPrisma経由でSQLiteを利用します。フォームのバリデーションにはZodを利用しています。
Nuxt 3でも同様に動作確認を行っていますが設定+実際のコード上での動作を理解するために一部ソースコードの解説を加えています。コア部分のソースコードはフレームワーク共通なので興味がある人はぜひ参考にしてみてください。
目次
Luciaとは
LuciaはSessionベースの認証ライブラリで、Astro以外にもNext.js, SvelteKitなどを含めさまざまなフレームワークに対応しています。認証方式としてusername+password以外にOAuthにも対応しています。TypeScriptにも対応しているので認証機能の実装を型安全に行うことができます。データベースはAdapter方式を利用しておりデータベースに対するコードがそれぞれ用意されています。認証に関するコアのコードは共通しているので一つのフレームワークで理解ができれば他のフレームワークでもその知識を活用することができます。
認証の流れ
設定を行う前にLuciaでは認証管理をどのように行っているのか簡単に説明を行っておきます。事前にサインアップ画面からユーザの登録は完了していることを前提にしています。
- ログイン画面に事前に登録したusename+passwordを入力して送信ボタンをクリック。
- 送信ボタンをクリックすると送信されたusername+passwordに一致するユーザが存在するかサーバ側の処理で確認。
- ユーザの存在確認後、Sessionデータを新規作成しデータベースに保存、作成したSessionデータに含まれるSessionIdを持つCookiesを作成。ログイン処理完了(認証済)。
- 各ページにSessionを検証する関数を設定して取得したSessionの値によって各ページに応じた処理を行う。ログイン後はログイン、サインアップページにはアクセスされないなど。
- Sessionの検証ではCookiesからSessionIdを取り出しデータベースからSessionを取得。
- 取得したSessionの有効期限をチェック。有効期限のチェックはactivePeriodとidlePeriodの2つで実施。activePeriodの期限が切れてもidlePeriod期限内であればSessionの有効期限の更新。デフォルトではactivePeriodが1日、idlePeriodが2週間+activePeriod有効期間。
- idlePeriodの有効期限が切れたら認証が終了。Cookieの有効期限切れても認証が終了。Cookieの有効期限はデフォルトではidlePeriodと同じ。
- 意図的に認証を終了したい場合はAPIエンドポイント(/logout)にPOSTリクエストを送信してSessionの無効化とCookieの削除を実施。Cookiesを手動で削除しても認証が終了。
プロジェクトの作成
Astroのプロジェクトの作成は”npm create astro@latest”コマンドで実行することができます。任意のプロジェクト名を設定することができますがコマンドを実行するとランダムな名前でプロジェクト名が設定されるのでそのままその名前を利用します。今回のプロジェクトの名前はtested-towerです。
プロジェクトを作成後、プロジェクトディレクトリに移動します。
Prismaの設定
本文書ではデータベースにはPrismaを利用して接続しますがLuciaがサポートしているデータベースには以下のものがあるので各自の環境にあったものをインストールしてください。

Adapterが提供されているのでデータベースのテーブル作成後の処理については大きな違いがありません。テーブルを作成する方法はそれぞれのデータベースによって異なりますがドキュメントに掲載されているのでドキュメント通りに設定することができます。
Prisma のインストール
npm コマンドを利用して Prisma のインストールを行います。

Prisma 用の設定ファイルを作成するために”npx prisma init”コマンドを実行します。実行するとプロジェクトディレクトリ下には prisma ディレクトリと.env ファイルが作成されます。prisma ディレクトリには Prisma の設定ファイルである schema.prisma ファイルが作成されています。.env ファイルはデータベースに接続するために必要となる環境変数を設定するために利用します。
SQLite データベースを利用するのでオプション–datasource-provider に sqlite を設定しています。もし指定しない場合には postgresql データベースが接続データベースとして設定された状態でファイルが作成されます。
SQLiteデータベースではファイルを利用してデータを保存するため.envファイルにはデータベースファイルを保管するパスとファイル名が環境変数DATABASE_URLに設定されています。
モデルの設定
schema.prismaファイルでは–datasource-providerを指定して実行した場合はSQLiteデータベースに関する設定は完了しているのでスキーマの設定を行います。スキーマについてはドキュメントに掲載されているのでそのまま利用します。
Lusiaで利用するモデルはUser, Session, Keyから構成されています。
Userモデルについてはデフォルトではidのみとなっていますが属性を追加することができます。
Sessionモデルについてはuser_idを介してUserモデルと紐付きログインする度にログインしたユーザに対して新たなSessionデータが作成されます。
Keyモデルについては一人のユーザが複数認証方式で登録した場合に認証方式毎にUserデータを作成するのではなく1つのUserデータに複数のKeyデータを紐づける場合に利用することができます。本文書ではデフォルトのusernameをKeyとして登録するので複数のKeyの動作確認は行なっていません。
テーブルの作成
schema.prismaファイルでのスキーマのの設定が完了したらSQLiteデータベースにテーブルを作成するために”npx prisma db push”コマンドを実行します。
コマンドを実行すると.env ファイルの DATABASE_URL で指定した場所に SQLite のデータベースファイル dev.db が作成されます
Prisma Studio からのデータベース接続
Prisma には Prisma Studio という Prisma 専用の GUI ツールを利用してデータベースにアクセスを行うことができます。Prisma Studio を起動するために npx prisma studio コマンドを実行します。
設定したスキーマからモデルが作成させていることが確認できます。

Luciaの設定
データベースの作成が完了したのでLuciaのインストールを行い、ページの作成や各種設定を行なっていきます。
Luciaのインストール
luciaライブラリのインストールを行います。
Prisma用のAdapterのインストールも行います。接続するデータベースによってインストールするAdapterは変わります。
SSRモードへの変更のを行っておきます。設定はastro.config.mjsファイルで行います。
設定していない場合には以下のWARNINGがコンソールに表示されます。
lucia.tsファイルの作成
luciaの設定を行うためにsrcディレクトリにlibディレクトリを作成してlucia.tsファイルを作成して以下のコードを記述します。
srcディレクトリにlibディレクトリを作成してlucia.tsファイルを作成します。
このファイルの内容は接続するデータベースや利用するフレームワーク/ライブラリによって設定が異なります。adapterは利用するデータベース、middlewareは利用するフレームワーク/ライブラリによって変わります。本文書の環境ではadapterにはPrisma, middlewareにはastroを設定します
型定義ファイルの作成
データベースから取得したユーザの情報やテーブルに属性を追加した場合に型の情報を正しく取得できるようにsrcディレクトリのenv.d.tsファイルにluciaの設定情報を追加します。
middlewareの設定
middlewareは各ページにアクセスする前に実行される処理を行うことができます。context.localsを利用することでデータを共有することができます。auth.handleRequestでAuthRequestというクラスがインスタンス化されauthに保存されますがページ内で利用することができます。
AuthRequestの型情報を登録するために型定義ファイルの更新を行います。
Layoutsページの作成
layoutsディレクトリを作成してLayout.astroファイルを作成します。これからsignup.astro, login.astro, index.astro, dashboard/index.astroの4つのページを作成するのでLayoutファイルには4つのページを移動できるようにリンクを設定します。
pagesディレクトリのindex.astroファイルの更新を行います。
pagesディレクトリの下にdashboardディレクトリを作成してindex.astroファイルを作成して以下の内容を記述します。
“npm run dev”コマンドで開発サーバを起動します。ブラウザでアクセスすると以下の画面が表示されます。”/”と”/dashboard”はページが存在するので移動することができます。

Signupページの作成
pageディレクトリの下にsignup.astroファイルを作成してユーザの登録を行うSignupページの作成を行います。
作成したSignupページには以下のように表示されます。

フォームで入力したバリデーションにはバリデーションライブラリのzodを利用しているのでインストールを行います。Zodを利用してusernameとpasswordの文字列の長さのチェックのみ行っています。バリデーションに失敗するとフォーム上にエラーが表示されるようになっています。

signup.astroの中ではcreateUserを利用して入力したユーザ情報で新規ユーザと新規のKeyを作成しています。ユーザとKeyはuserIdを介して紐づいています。keyに設定しているproviderIdとproviderUserIdを利用してkey内で一意となるデータを作成しています。providerIdは今回はusernameですが、email、githubといったように設定するProviderによって値を変更することができます。
ユーザ作成が完了するとauth.createSessionでSessionデータを作成します。Sessionを作成後、middlewareから受け取ったAstro.locals.auth.setSession(AuthRequest.setSession)を利用してCookieの作成を行います。CookieにはSessionで作成したSessionIdが入ります。Cookieを作成後/dashboardにリダイレクトしています。
データベース側でエラーが発生した場合にはLuciaErrorがthrowされるようにデータベースのAdapterで設定が行われています。事前にAdapterで設定されているエラーに対応できるようにtry, catchを利用してエラーをハンドリングしています。
ユーザを作成するとデータベースには下記の情報が保存されます。
ユーザIDはランダムな文字列でSessionとKeyが紐づいています。

Keyテーブルにはusername:と入力フォームで入力した文字列の小文字と一緒に保存されます。

idは40文字のSessionIdでactive_expiresとidle_expiresには有効期限が保存されます。

ブラウザのデベロッパーツールのApplicationのCookiesから作成されたCookiesを確認することができます。

リダイレクトの設定
ログインしたユーザがSignupページにアクセスできないようにリダイレクトの設定を行います。サインアップが完了すると認証が完了しているのでSignupページにアクセスするとDashboardページにリダイレクトされます。
Loginページの作成
Loginページの作成を行いますがほとんど内容はSignupページと同じで違いはLoginページではユーザの作成が必要ありません。SignUpページで設定したリダイレクトの設定も行っています。

login.astroページではauth.useKeyを利用してusenameとpasswordの検証を行い、データベース内の情報と一致したら紐づいているkeyデータを取り出します。keyデータにはuserIdが含まれているのでuserIdをauth.createSessionの引数に設定して新しいSessionデータを作成します。Sessionを作成後、middlewareから受け取ったAstro.locals.auth.setSession(AuthRequest.setSession)を利用してCookieの作成を行います。CookieにはSessionで作成したSessionIdが入ります。Cookieを作成後/dashboardにリダイレクトしています。
Dashboardページの設定
Dashboardページではログインが完了していないユーザがアクセスできないようにアクセス制限を行います。ログインが完了している場合はsessionに含まれているuserIdがブラウザ上に表示されるように設定しています。sessionが存在しない場合は/loginにリダイレクトされます。
ログアウト機能の追加
DashboardページにLogoutボタンを追加します。ボタンをクリックするとAPIエンドポイントの/logoutにPOSTリクエストが送信されます。

APIエンドポイントを作成するためにpagesディレクトリにlogout.tsファイルを作成します。
POSTリクエストが送信されていたらSessionの検証を行い、Sessionが存在する場合はinvalidSessionでSessionテーブルからSession情報を削除してsetSessionの引数をnullにすることでCookiesを削除しています。
AstroでのLuciaを利用して認証機能を実装することができました。