Nuxt BridgeをNuxt3に移行中。@nuxtjs/authがまだPlannedで、@sidebase/nuxt-authが推奨されていますが、挙動がかなり違うので、調べてみました。

Roadmap · Nuxt
【公式】Nuxt-auth Module · Nuxt
GitHub – sidebase/nuxt-auth
-> MIT license

nuxt-authを導入

現段階では、0.6.0-beta.4が入りました。

% yarn add -D @sidebase/nuxt-auth

nuxt.config.ts

  modules: [
    '@nuxtjs/i18n',
+    '@sidebase/nuxt-auth'

  auth: {
    computed: { origin: true, fullBaseUrl: config.envConfig.apiBaseURL },
    provider: {
      type: 'local',
      endpoints: {
        signIn: { path: commonConfig.authSignInURL, method: "post" },
        signOut: { path: commonConfig.authSignOutURL, method: "post" },
        getSession: { path: commonConfig.authUserURL, method: "get" }
      },
      token: {
        // signInResponseTokenPointer: '/token', // レスポンスJSON内のtokenのキー
        // type: 'Bearer',
        headerName: 'access-token', // Authorization
        maxAgeInSeconds: 60 * 60 * 24 // バックエンドと同じか長くしないと不整合な状態になる(フロントが未ログイン、バックエンドがログイン中)
      }
    }
  },

最初、APIリクエストのパスが変になりましたが、computedで対応できました。

ログインする

Nuxt2 + @nuxtjs/auth

      await this.$auth.loginWith('local', {
        data: {
          <省略>
        }
      })

Nuxt3 + @sidebase/nuxt-auth

      const { signIn } = useAuth()
      await signIn({
        <省略>
      })

Error: Invalid reference token: token

ログインAPIのレスポンスにtokenの項目が必要になります。
nuxt.config.tsのsignInResponseTokenPointerでJSON内の場所は変更できますが、存在は必須だったので、Rails側を修正。

Rails: app/views/users/auth/success.json.jbuilder

+ json.token 'dumy'

この後に、Devise Token Authがtokenを生成してヘッダにaccess-tokenにセットするので、この段階では取得できなそう。(xx)
また、access-tokenは、uid(メアド)とclient(端末)とセットで認証するので、この辺も保持する必要がある。(xx)

Error: Navigating to external URL is not allowed by default. Use `navigateTo (url, { external: true })`.

node_modules/@sidebase/nuxt-auth/dist/runtime/composables/local/useAuth.mjs

  const { callbackUrl, redirect = true } = signInOptions ?? {};
  if (redirect) {
    const urlToNavigateTo = callbackUrl ?? await getRequestURLWN(nuxt);
    return navigateTo(urlToNavigateTo);
  }

ここのnavigateToの中で落ちてます。signInOptionsで、callbackUrlが渡せれば良さそう。

const signIn = async (credentials, signInOptions, signInParams) => {

signInOptionsは2つ目のパラメータなので、下記のように修正します。

pages/users/sign_in.vue

        await signIn({
			<省略>
-        })
+        }, { callbackUrl: '/users/sign_in' })

GET http://localhost:3000/users/auth/validate_token.json 406 (Not Acceptable)

ACCEPTヘッダに「application/json」が必要なようにしてたので、一旦、コメントアウトします。

Rails: app/controllers/users/auth/token_validations_controller.rb

-  prepend_before_action :response_not_acceptable_for_not_api
+  # prepend_before_action :response_not_acceptable_for_not_api

GET http://localhost:3000/users/auth/validate_token.json 401 (Unauthorized)

リクエストヘッダ
	Access-Token: Bearer dumy
レスポンス
	{success: false, alert: "セッションがタイムアウトしました。ログインし直してください。"}

signIn()でトークン取得、そのままgetSessionしてログイン状態になる。
getSessionにリクエストヘッダ追加(getSessionOptions?)できれば何とかなりそうだが、どこで?(xx)

そもそも、Devise Token Authの仕様と異なるので、自前でAPI叩いて、ログイン状態に変更した方が良さそう。

ログイン・ログアウト状態にする

Nuxt2 + @nuxtjs/auth

this.$auth.setUser({ id: 1, name: 'テスト' })

await this.$auth.logout()

Nuxt3 + @sidebase/nuxt-auth

const { data, status } = useAuthState()
data.value = { user: { id: 1, name: 'テスト' } }
console.log(status.value, data.value, data.value.user)

data.value = null
console.log(status.value, data.value)

-> authenticated Proxy(Object) {user: {…}} Proxy(Object) {id: 1, name: ‘テスト’}
-> unauthenticated null

挙動をソースで確認

useStateのauth:dataにレスポンス結果が入る。
入っている場合はログイン中(authenticated)と判定される。
(loadingはgetSessionの処理中にtrueになる)

node_modules/@sidebase/nuxt-auth/dist/runtime/composables/local/useAuthState.mjs

import { makeCommonAuthState } from "../commonAuthState.mjs";
export const useAuthState = () => {
  const commonAuthState = makeCommonAuthState();

  return {
    ...commonAuthState,

node_modules/@sidebase/nuxt-auth/dist/runtime/composables/commonAuthState.mjs

export const makeCommonAuthState = () => {
  const data = useState("auth:data", () => void 0);

  const status = computed(() => {
    if (loading.value) {
      return "loading";
    } else if (data.value) {
      return "authenticated";
    } else {
      return "unauthenticated";
    }
  });

  return {
    data,
    status,

今回は、状態管理だけしか使えなそうなので、自前でも十分だけど、今後の拡張性を考えると、これ使っておいた方が幸せになれそう。

ログイン状態を確認する

Nuxt2 + @nuxtjs/auth

console.log(this.$auth.loggedIn)

Nuxt3 + @sidebase/nuxt-auth

const { status } = useAuthState() // useAuth()でもOK
console.log(status.value === 'authenticated')

loading / authenticated / unauthenticatedが返却される。

ユーザー情報を取得する

signInやgetSessionで返却されるJSONの中のuserの値。

Nuxt2 + @nuxtjs/auth

console.log($auth.user)

Nuxt3 + @sidebase/nuxt-auth

const { data } = useAuthState() // useAuth()でもOK
console.log(data.value.user)

-> Proxy(Object) {id: 1, name: ‘テスト’}

ユーザー情報を更新する

tokenを使ってAPIを叩いて、最新のユーザー情報を取得するやつ。

Nuxt2 + @nuxtjs/auth

this.$auth.fetchUser()

Nuxt3 + @sidebase/nuxt-auth

      const { getSession } = useAuth()
      const dataValue = await getSession()
      console.log(dataValue)

-> ログイン状態にしてもundefined

挙動をソースで確認

node_modules/@sidebase/nuxt-auth/dist/runtime/composables/local/useAuth.mjs

const getSession = async (getSessionOptions) => {

  const { data, loading, lastRefreshedAt, token, rawToken } = useAuthState();
  if (!token.value) {
    return;
  }

tokenがないのでundefinedになる。

node_modules/@sidebase/nuxt-auth/dist/runtime/composables/local/useAuthState.mjs

export const useAuthState = () => {

  const rawToken = useState("auth:raw-token", () => _rawTokenCookie.value);

  const token = computed(() => {
    if (rawToken.value === null) {
      return null;
    }
    return config.token.type.length > 0 ? `${config.token.type} ${rawToken.value}` : rawToken.value;
  });
  const schemeSpecificState = {
    token,
    rawToken
  };
  return {
    ...commonAuthState,
    ...schemeSpecificState

tokenは、useStateのauth:raw-tokenから取得しているので、セットすれば呼べそう。
ただ、Devise Token Authの仕様と異なるので、ログインと同様に自前で対応するのが良さそう。

ログアウトする

Nuxt2 + @nuxtjs/auth

await this.$auth.logout()

Nuxt3 + @sidebase/nuxt-auth

      const { signOut } = useAuth()
      console.log(await signOut())

POST http://localhost:3000/users/auth/sign_out.json 406 (Not Acceptable)

ACCEPTヘッダに「application/json」が必要なようにしてたので、一旦、コメントアウトします。

Rails: app/controllers/users/auth/sessions_controller.rb

-  prepend_before_action :response_not_acceptable_for_not_api
+  # prepend_before_action :response_not_acceptable_for_not_api

POST http://localhost:3000/users/auth/sign_out.json 401 (Unauthorized)

リクエストヘッダ
	Access-Token: null
レスポンス
	{success: false, alert: "既にログアウトされています。"}

挙動をソースで確認

node_modules/@sidebase/nuxt-auth/dist/runtime/composables/local/useAuth.mjs

const signOut = async (signOutOptions) => {

  const { data, rawToken, token } = await callWithNuxt(nuxt, useAuthState);
  const headers = new Headers({ [config.token.headerName]: token.value });

  const res = await _fetch(nuxt, path, { method, headers });

token(useStateのauth:raw-token)があれば、リクエストヘッダはセットされる。
ただ、Devise Token Authの仕様と異なるので、自前でAPI叩いて、ログアウト状態に変更した方が良さそう。

リロードや再表示時にログイン状態に戻す

Nuxt2 + @nuxtjs/auth

axiosのpluginでLocalStorageから取得したtokenをリクエストヘッダを追加するようにしていた。
初期表示時に、fetchUser()が動いて、Vuexにユーザー情報がセットされ、ログイン状態が復元される流れ。

Nuxt3 + @sidebase/nuxt-auth

こちらでも流れは同じで、Cookie(auth:token)から取得したtokenをリクエストヘッダを追加して、
初期表示時に、getSession()が動いて、useStateにユーザー情報がセットされ、ログイン状態が復元される流れ。
Devise Token Auth使う関係で、ここも自前になりそうですが、middleware(1回なのでpluginsの方が良さそう)を使えば行けそう。

node_modules/@sidebase/nuxt-auth/dist/runtime/composables/local/useAuthState.mjs

  const _rawTokenCookie = useCookie("auth:token", { default: () => null, maxAge: config.token.maxAgeInSeconds, sameSite: config.token.sameSiteAttribute });

  watch(rawToken, () => {
    _rawTokenCookie.value = rawToken.value;

tokenの期限切れで更新する

maxAgeInSeconds以降に自動で、fetchUser()やgetSession()が動くと思われる。
対応しなくても、次回APIリクエスト時にログイン画面に遷移するように作っているので対応しなくても良いけど、丁寧にやるなら、middleware等で、経過時間を見るか、Devise Token Authが返すexpiryを見て、ユーザー情報を更新すれば良さそう。

対応方針

1. @sidebase/nuxt-authに合わせてAPIを直す
2. APIリクエストは自前でして、@sidebase/nuxt-authの状態管理のみ使用する
3. @sidebase/nuxt-authを使わずに、useStateも実装する

1は影響が大きいのと、Devise Token Authにも対応しておきたいので、
今回は不採用として、いずれ両方に対応できたら良いな。
3でも良い気もするけど、将来の拡張性(ソーシャルログイン)とか考えると、一部でも標準?の仕組みを使っておきたい所なので、今回は2で行こうと思います。

Nuxt BridgeをNuxt3に移行。nuxt-authを導入

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です