Nuxt BridgeをNuxt3に移行中。@nuxtjs/authがまだPlannedで、@sidebase/nuxt-authが推奨されていますが、挙動がかなり違うので、調べてみました。
Roadmap · Nuxt
【公式】Nuxt-auth Module · Nuxt
GitHub – sidebase/nuxt-auth
-> MIT license
- nuxt-authを導入
- ログインする
- ログイン・ログアウト状態にする
- ログイン状態を確認する
- ユーザー情報を取得する
- ユーザー情報を更新する
- ログアウトする
- リロードや再表示時にログイン状態に戻す
- tokenの期限切れで更新する
- 対応方針
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で行こうと思います。