Nuxt 3では、Azure Static Web Apps(以下Azure SWA)にデプロイするための設定が用意されています。
しかしAzure SWAで提供されている機能を使おうとすると、そのままではいろいろと不都合な状態にあります。
こうした状態を解消するためのNuxtモジュールである、nuxt-swa
ライブラリを公開しました。(記事執筆時点でv0.5.0)
-
GitHubのリポジトリ
- IssueやPull Requestは随時受け付けています(日本語OK)
- 公式ドキュメント(兼Playground)
何が困るのか?
- 認証機能(
/.auth/...
)などをサーバーサイドから呼べない - ルート規則がPage Routing時に機能しない
認証機能(/.auth/...
)などをサーバーサイドから呼べない
Azure SWAでは、組み込みの認証機能を利用するAPIエンドポイントが以下のように提供されています。
-
/.auth/login/{認証プロバイダ名}
: ログイン -
/.auth/me
: ログイン中のユーザー情報(Client Principal)を取得 -
/.auth/logout
: ログアウト
しかしながら、NuxtはSSR時に、内部API(/
始まり)に対するfetchリクエストを全て内部実装の直接呼び出しに変換します。(Direct call)
そのため、以下のようなコードはサーバーサイド側で[Vue Router warn]: No match found for location with path "..."
といったエラーを発生させます。
<template>
<button @click="navigateTo('/.auth/logout')">ログアウト</button>
</template>
<script setup lang="ts">
const { clientPrincipal } = await $fetch('/.auth/me')
</script>
また、現在パブリックプレビューであるData API Builderのエンドポイント(/data-api/
配下)についても同様です。
ルート規則がPage Routing時に機能しない
Azure SWAは、特定のルートへのアクセスを認証済みユーザーのみに制限する機能も持っています。
{
"routes": [
{
// /profile 以下はログインユーザーのみ閲覧可能
"route": "/profile*",
"allowedRoles": ["authenticated"]
},
{
// /admin/index.html はadministratorロールを持つユーザーのみ閲覧可能
"route": "/admin/index.html",
"allowedRoles": ["administrator"]
},
{
// /api/ 以下のPUT, POST, PATCH, DELETEリクエストはadministratorロールを持つユーザーのみ
"route": "/api/*",
"methods": ["PUT", "POST", "PATCH", "DELETE"],
"allowedRoles": ["administrator"]
}
]
}
しかし、これが機能するのはAzure SWAに対して直接リクエストを発行した場合のみになります。
そのためクライアント側でルーティングを実施した場合(例: Vue Router)や、前述のようなAPIのDirect callが発生した場合は、認証がスキップされてしまいます。
nuxt-swa
が提供する機能
下記は一例ですので、ほかの機能についてはドキュメントをご覧ください。
SWA組み込みAPIに対するプロキシ
/.auth/
以下および、/data-api/
以下のルートにリクエストがあった場合、ドメインを付与したフルパスにプロキシするイベントハンドラーを組み込んでいます。
「Azure Functions側で付与される
x-ms-original-url
を読み取ることでドメインを判別する」仕組みなので、サーバー側API(/serverフォルダ配下)から$fetch
を呼び出す際はリクエストヘッダーを明示的にバイパスする必要がある点に留意してください。
Global Page Middleware
Pageコンポーネントで以下のように設定することで、モジュール内部で指定したロールを持っていない場合はレンダリング前に401/403エラーを返します。
<script setup lang="ts">
definePageMeta({ allowedRoles: 'authenticated' })
</script>
<template>
<div>Private Page!</div>
</template>
既存の
staticwebapp.config.json
との併用も可能ですが、初期リクエスト時(Azure SWAのエラーページ)とページルーティング時(Nuxtのエラーページ)で表示されるエラーページが変わってしまうため、上記設定に寄せることをオススメします。
組み込み認証を利用するためのComposable関数
const {
clientPrincipal, // 認証情報
isLoggedIn, // ログインしているかどうか
hasRole, // ロールを持つかどうかを返すcomputed<boolean>を生成する関数
login, // navigateTo(`/.auth/login/{provider}`)
logout, // navigateTo(`/.auth/logout`)
} = await useEasyAuth()
サーバー側でユーザー情報を取得するユーティリティ関数の提供
export default defineEventHandler(event => {
// リクエストしたユーザーの認証情報を取得
const clientPrincipal = getClientPrincipal(event)
})
export default defineEventHandler(event => {
// administratorロールを持つか
if (!hasRole(event, 'administrator')) {
throw createError({ statusCode: 403 })
}
})
nitro.azure.config
に対する型定義の提供
上記設定がない場合は、モジュール側でも警告します。
今後の展望(ToDo)
- Nuxt Modulesへの登録
-
async
/await
を使うとNuxtコンテキストを失ってしまうことがあるらしいので、構造を再検討する - 型定義がうまく反映されない(特に/server側)ので、適切に設定する
- インテグレーションテストの整備
- Azure SWA環境でないと厳しいかも
- ビルド後に
staticwebapp.config.json
を/.output/public
に移動させるオプションを用意