Nuxt Authorization
NuxtとNitroの両方で簡単に認証処理を行います。
このモジュールはACLやRBACを実装していません。独自の認証ロジックを実装するために使用できる低レベルのプリミティブを提供します。
!注記 将来的には、このモジュールはNitroモジュールとNuxtモジュールとして利用可能になる可能性がありますが、Nitroモジュールはまだ準備ができていません。
このモジュールとそれが解決する問題の詳細については、Nuxtにおける認証に関するブログ記事をご覧ください。
機能
- ⛰ クライアント(Nuxt)とサーバー(Nitro)の両方で動作します
- 🌟 権限を一度記述して、どこでも使用できます
- 👨👩👧👦 認証レイヤーに依存しません
- 🫸 コンポーネントを使用して、UIの一部を条件付きで表示できます
- 💧 プリミティブにアクセスして、完全にカスタマイズできます
クイックセットアップ
1つのコマンドでNuxtアプリケーションにモジュールをインストールします
npx nuxi module add nuxt-authorization
これで完了です!Nuxtアプリでモジュールを使用できます ✨
ドキュメント
!注記 プレイグラウンドでモジュールの動作を確認できます。
設定
モジュールを使用し、最初の権限を定義する前に、2つのリゾルバーを提供する必要があります。これらの関数は内部的にユーザーの取得に使用されますが、実装する必要があります。これにより、モジュールは認証レイヤーに依存しなくなります。
Nuxtアプリの場合、plugins/authorization-resolver.ts
に新しいプラグインを作成します。
export default defineNuxtPlugin({
name: 'authorization-resolver',
parallel: true,
setup() {
return {
provide: {
authorization: {
resolveClientUser: () => {
// Your logic to retrieve the user from the client
},
},
},
}
},
})
この関数は、クライアントで認証を確認するたびに呼び出されます。ユーザーオブジェクトを返すか、ユーザーが認証されていない場合はnull
を返す必要があります。非同期にすることができます。
Nitroサーバーの場合、server/plugins/authorization-resolver.ts
に新しいプラグインを作成します。
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('request', async (event) => {
event.context.$authorization = {
resolveServerUser: () => {
// Your logic to retrieve the user from the server
},
}
})
})
!注記
event.context
の詳細については、こちらをご覧ください。
このリゾルバーはrequest
フック内で設定され、イベントを受け取ります。これを使用して、セッションまたはリクエストからユーザーを取得できます。ユーザーオブジェクトを返すか、ユーザーが認証されていない場合はnull
を返す必要があります。非同期にすることができます。
一般的に、アプリの起動時にユーザーを取得し、それを保存するためにプラグインを使用します。リゾルバー関数は、保存されたユーザーのみを返し、再度フェッチするべきではありません(そうでない場合、深刻なパフォーマンスの問題が発生する可能性があります)。
nuxt-auth-utils
を使用した例
モジュールnuxt-auth-utils
は、Nuxtの認証レイヤーを提供します。このモジュールを使用する場合は、次のリゾルバーを使用できます。
Nuxtプラグイン
export default defineNuxtPlugin({
name: 'authorization-resolver',
parallel: true,
setup() {
return {
provide: {
authorization: {
resolveClientUser: () => useUserSession().user.value,
},
},
}
},
})
Nitroプラグイン
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('request', async (event) => {
event.context.$authorization = {
resolveServerUser: async () => {
const session = await getUserSession(event)
return session.user ?? null
},
}
})
})
簡単!
権限の定義
!注記 今後のNuxt4では、権限を
app/utils/abilities.ts
ファイルに保存できます。その後、~/utils/abilities
を使用してサーバーフォルダーにインポートできます。プロジェクトOrionの例を参照してください。
!注記 Nuxt4では、クライアントとサーバー間でコードを簡単に共有するための新しい
shared
ディレクトリが導入されます。このissueを参照してください。
これでリゾルバーが設定されたので、最初の権限を定義できます。権限とは、少なくともユーザーを受け取り、ユーザーがアクションを実行できるかどうかを示すブール値を返す関数です。追加の引数も受け取ることができます。
権限を作成するには、新しいファイルutils/abilities.ts
を作成することをお勧めします。
export const listPosts = defineAbility(() => true) // Only authenticated users can list posts
export const editPost = defineAbility((user: User, post: Post) => {
return user.id === post.authorId
})
多くの権限がある場合は、utils/abilities/
ディレクトリを作成し、各権限ごとにファイルを作成することをお勧めします。utils
ディレクトリに権限を配置すると、クライアントで自動インポートが機能し、サーバーでは~/utils/abilities
で簡単なインポートが可能になります。
デフォルトでは、ゲストはどのアクションも実行できず、権限は呼び出されません。この動作は、権限ごとに変更できます。
export const listPosts = defineAbility({ allowGuest: true }, (user: User | null) => true)
これで、認証されていないユーザーが投稿をリストできるようになりました。
権限の使用
権限を使用するには、allows
、denies
、authorize
という3つのバウンサー関数にアクセスできます。これらは両方ともクライアントとサーバーで使用できます。*実装は異なりますが、APIは同じであり、開発者にとって完全に透過的です。*
allows
関数は、ユーザーがアクションを実行できるかどうかを示すブール値を返します。
if (await allows(listPosts)) {
// User can list posts
}
denies
関数は、ユーザーがアクションを実行できないかどうかを示すブール値を返します。
if (await denies(editPost, post)) {
// User cannot edit the post
}
authorize
関数は、ユーザーがアクションを実行できない場合にエラーをスローします。
await authorize(editPost, post)
// User can edit the post
権限の戻り値ごとにエラーメッセージとステータスコードをカスタマイズできます。これは、リソースの存在をユーザーに気付かせないように、403の代わりに404を返すのに役立ちます。
export const editPost = defineAbility((user: User, post: Post) => {
if(user.id === post.authorId) {
return true // or allow()
}
return deny('This post does not exist', 404)
})
allow
とdeny
はtrue
とfalse
を返すことと似ていますが、deny
では、エラーのカスタムメッセージとステータスコードを返すことができます。
ほとんどの場合、APIエンドポイントはauthorize
を使用します。これは、パラメーターが不要な場合はエンドポイントの最初の行、またはユーザーがリソースにアクセスできるかどうかを確認するためのデータベースクエリの後で使用できます。H3Error
なので、Nitroサーバーによってキャッチされるため、エラーをキャッチする必要はありません。
allows
関数とdenies
関数は、クライアントで条件付きレンダリングやロジックを実行するのに役立ちます。これらを使用して、認証ロジックをきめ細かく制御することもできます。
コンポーネントの使用
このモジュールは、UIの一部を条件付きで表示するのに役立つ2つのコンポーネントを提供します。投稿を編集するボタンがあるとします。権限のないユーザーには、このボタンが表示されないようにする必要があります。
<template>
<Can
:ability="editPost"
:args="[post]" // Optional if the ability does not take any arguments
>
<button>Edit</button>
</Can>
</template>
Can
コンポーネントは、ユーザーが投稿を編集できる場合にのみボタンをレンダリングします。ユーザーが投稿を編集できない場合、ボタンはレンダリングされません。
対照的に、Cannot
コンポーネントを使用して、ユーザーが投稿を編集できない場合にのみボタンをレンダリングできます。
<template>
<Cannot
:ability="editPost"
:args="[post]" // Optional if the ability does not take any arguments
>
<p>You're not allowed to edit the post.</p>
</Cannot>
</template>
Bouncer
コンポーネントは、単一のコンポーネント内でcanとcannotの両方のシナリオをより柔軟で一元化された方法で処理します。Can
コンポーネントとCannot
コンポーネントを個別に使用する代わりに、Bouncerコンポーネントとその名前付きスロットを利用して、統合されたブロックで両方の状態を処理できます。
<Bouncer
:ability="editPost"
:args="[post]" // Optional if the ability does not take any arguments
>
<template #can>
<button>Edit</button>
</template>
<template #cannot>
<p>You're not allowed to edit the post.</p>
</template>
</Bouncer>
貢献
ローカル開発
# Install dependencies
npm install
# Generate type stubs
npm run dev:prepare
# Develop with the playground
npm run dev
# Build the playground
npm run dev:build
# Run ESLint
npm run lint
# Run Vitest
npm run test
npm run test:watch
# Release new version
npm run release
クレジット
このモジュール(コードとデザインの両方)は、Adonis Bouncerから多大な影響を受けています。これはよく書かれたパッケージであり、毎回車輪の再発明は不要だと思います。