Problem
Below Nuxt middleware
const inspectAuthentication: Middleware = async (): Promise<void> => {
await AuthenticationService.getInstance().inspectAuthentication();
};
is being executed on the server side before return each page's HTML and checks has been user authenticated. If has been, it stores the CurrentAuthenticatedUser in Vuex module:
import {
VuexModule,
getModule as getVuexModule,
Module as VuexModuleConfiguration,
VuexAction,
VuexMutation
} from "nuxt-property-decorator";
@VuexModuleConfiguration({
name: "AuthenticationService",
store,
namespaced: true,
stateFactory: true,
dynamic: true
})
export default class AuthenticationService extends VuexModule {
public static getInstance(): AuthenticationService {
return getVuexModule(AuthenticationService);
}
private _currentAuthenticatedUser: CurrentAuthenticatedUser | null = null;
public get currentAuthenticatedUser(): CurrentAuthenticatedUser | null {
return this._currentAuthenticatedUser;
}
@VuexAction({ rawError: true })
public async inspectAuthentication(): Promise<boolean> {
// This condition is always falsy after page reloading
if (this.isAuthenticationInspectionSuccessfullyComplete) {
return isNotNull(this._currentAuthenticatedUser);
}
this.onAuthenticationInspectionStarted();
// The is no local storage on server side; use @nuxtjs/universal-storage instead
const accessToken: string | null = DependenciesInjector.universalStorageService.
getItem(AuthenticationService.ACCESS_TOKEN_KEY_IN_LOCAL_STORAGE);
if (isNull(accessToken)) {
this.completeAuthenticationInspection();
return false;
}
let currentAuthenticatedUser: CurrentAuthenticatedUser | null;
try {
currentAuthenticatedUser = await DependenciesInjector.gateways.authentication.getCurrentAuthenticatedUser(accessToken);
} catch (error: unknown) {
this.onAuthenticationInspectionFailed();
// error wrapping / rethrowing
}
if (isNull(currentAuthenticatedUser)) {
this.completeAuthenticationInspection();
return false;
}
this.completeAuthenticationInspection(currentAuthenticatedUser);
return true;
}
@VuexMutation
private completeAuthenticationInspection(currentAuthenticatedUser?: CurrentAuthenticatedUser): void {
if (isNotUndefined(currentAuthenticatedUser)) {
this._currentAuthenticatedUser = currentAuthenticatedUser;
DependenciesInjector.universalStorageService.setItem(
AuthenticationService.ACCESS_TOKEN_KEY_IN_LOCAL_STORAGE, currentAuthenticatedUser.accessToken
);
}
// ...
}
}
Above code works fine on server side, but then, on the client side, if to try to get AuthenticationService.getInstance().currentAuthenticatedUser, it will be null!
I expected that Nuxt.js synchronizes the Vuex store including AuthenticationService with server side, however, it does not.
Target
AuthenticationService must be synchronized with server side, so if user has been authenticated, in the client side AuthenticationService.getInstance().currentAuthenticatedUser it must be non-null even after page reloading.
There no need to synchronize whole Vuex store in server side (for example, the module responsible floating notification bar is required in the client side only) but if the selective methodology has not been developed, at least synchronizing of whole Vuex store will be enough for now.
Please don't recommend me the libraries or Nuxt modules for authentication like Nuxt Auth module because here we are talking about synchronizing of the Vuex store with server, not about best Nuxt modules for authentication. Also, the syncronizing of the vuex store between client and server could be used not just for authentication.
Update
preserveState solution attempt
Unfortunately,
import { store } from "~/Store";
import { VuexModule, Module as VuexModuleConfiguration } from "nuxt-property-decorator";
@VuexModuleConfiguration({
name: "AuthenticationService",
store,
namespaced: true,
stateFactory: true,
dynamic: true,
preserveState: true /* New */
})
export default class AuthenticationService extends VuexModule {}
causes
Cannot read property '_currentAuthenticatedUser' of undefined
error on the server side.
The error refers to
@VuexAction({ rawError: true })
public async inspectAuthentication(): Promise<boolean> {
if (this.isAuthenticationInspectionSuccessfullyComplete) {
// HERE ⇩
return isNotNull(this._currentAuthenticatedUser);
}
}
I checked this value. It's a big object; I'll leave the the noticable part only:
{
store: Store {
_committing: false,
// === ✏ All actual action here
_actions: [Object: null prototype] {
'AuthenticationService/inspectAuthentication': [Array],
'AuthenticationService/signIn': [Array],
'AuthenticationService/applySignUp': [Array],
// ...
// === ✏ Some mutations ...
onAuthenticationInspectionStarted: [Function (anonymous)],
completeAuthenticationInspection: [Function (anonymous)],
// ...
context: {
dispatch: [Function (anonymous)],
commit: [Function (anonymous)],
getters: {
currentAuthenticatedUser: [Getter],
isAuthenticationInspectionSuccessfullyComplete: [Getter]
},
// === ✏ The state in undefined!
state: undefined
}
}
I suppose I need to tell how I initializing the vuex store. The working Nuxt methodology for dynamic modules is:
// store/index.ts
import Vue from "vue";
import Vuex, { Store } from "vuex";
Vue.use(Vuex);
export const store: Store<unknown> = new Vuex.Store<unknown>({});
nuxtServerInit solution attempt
Here is the another problem - how to integrate nuxtServerInit in above store initialization method? I suppose, to answer this question it's required the Vuex and vuex-module-decorators. In below store/index.ts, the nuxtServerInit even will not be called:
import Vue from "vue";
import Vuex, { Store } from "vuex";
Vue.use(Vuex);
export const store: Store<unknown> = new Vuex.Store<unknown>({
actions: {
nuxtServerInit(blackbox: unknown): void {
console.log("----------------");
console.log(blackbox);
}
}
});
I extracted this problem to other question.
