question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Nuxt CookieStorage example - authenticated calls on the server do not work

See original GitHub issue

Steps to reproduce

  1. Create a universal nuxt app - yarn create nuxt-app
  2. Follow setup steps to add v2 feathers-vuex code - and Nuxt specific steps (i.e. cookie storage, nuxtServerInit/initAuth)
  3. Create a page e.g. /test and in the fetch() function (called server side) place a call which requires auth:
<script>
import { models } from 'feathers-vuex';
export default {
  async fetch ({ store, params }) {
    const { User } = models.api;
    const res = await User.get(1);
    // Fails. Same call on the client will work.
  }
}
</script>
  1. In a browser, set a valid jwt cookie, visit /test and refresh

Expected behavior

The api call in fetch should be made on the server authenticated and succeed.

Actual behavior

A NotAuthenticated error is thrown.

Notes

  • nuxtServerInit/initAuth is successful in reading the jwt from the cookie and setting it in the store (i.e. state.auth.accessToken is valid)
  • but, it doesn’t seem to be used in subsequent api requests made on the server?

In trying to investigate this more, I found that the authenticate action when called on the server also fails:

dispatch('auth/authenticate'); // throws an error - No accessToken found in storage

If the accessToken from the store is passed, this gets further but then bails with a document not defined error.

nuxtServerInit({ commit, dispatch, state }, { req }) {
    return initAuth({
      commit,
      dispatch,
      req,
      moduleName: 'auth',
      cookieName: COOKIE_ACCESS_TOKEN
    }).then(() => {
      dispatch('auth/authenticate', { accessToken: state.auth.accessToken, strategy: 'jwt' })
    });
// throws an error - document is not defined . Same if `feathersClient` is passed to `initAuth()`

The document is not defined error is thrown in cookie-storage.js, since setAccessToken() is called when feathersClient.authenticate() succeeds.

// cookie-storage.js
  value: function _setCookie(value) {
      document.cookie = value;
    }

Which obviously will fail on the server.

I am new to feathers-vuex and trying to evaluate it for use in a project. I’ve spent a decent amount of time looking through old cases/examples on this topic but cannot seem to find anything definitive which covers this scenario?

Thanks in advance!

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:2
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

9reactions
nakedguncommented, Nov 13, 2019

I managed to fix this by tweaking the examples in the docs and studying the internals a bit more. Docs are fine for a Nuxt app in SPA mode, i.e. auth/service calls on the client, but you’ll hit problems in universal mode when trying to authenticate the user on the server. This is basically because of the recommendation to use CookieStorage as the auth storage adapter.

TL;DR - if you want a Nuxt/SSR/feathers-vuex setup where you can authenticate the user on the server and make authenticated calls in fetch/asyncData, the example below might be a useful starting point.

Case can be closed - there’s no bug here, tho perhaps covering universal setup/gotchas in the docs would be nice! Thanks for hard work on this library.

Tweaks for Nuxt universal mode app (auth on the server):

  • feathersClient auth module uses the MemoryStorage adapter when running on the server and the CookieStorage adapter when running on the client
  • auth/authenticate needs to be called somewhere after initAuth(). I added this in nuxtServerInit()- but it could go elsewhere, i.e. middleware. With the MemoryStorage adapter being used on the server, auth/authenticate doesn’t throw the document is not defined error anymore.
  • tweaks to work with the Nuxt module syntax for vuex (the feathers-vuex docs are for the classic mode which is depreciated)

Example:

"@feathersjs/authentication-client": "^4.3.11",
"@feathersjs/rest-client": "^4.3.11",
"cookie-storage": "^5.0.3",
"feathers-client": "^2.4.0",
"feathers-hooks-common": "^4.20.7",
"feathers-vuex": "^2.2.4",
"nuxt": "^2.0.0"
// feathers-client.js
import feathers from '@feathersjs/feathers';
import rest from '@feathersjs/rest-client';
import authClient, { MemoryStorage } from '@feathersjs/authentication-client';
import feathersVuex from 'feathers-vuex';
import axios from 'axios';
import { CookieStorage } from 'cookie-storage';
import config from './config';
import { COOKIE_ACCESS_TOKEN } from './modules/auth/persistence/constants';

const host = config.get('API_URL'); // set the feathersjs API server
const restClient = rest(host);
const feathersClient = feathers()
  .configure(restClient.axios(axios))
  .configure(
    authClient({
      // use in memory storage for SSR (safe, won't error out trying to write cookies to document) 
      // use cookies on the client (which are SSR friendly and read for each client request in nuxtServerInit())
      storage: process.server ? new MemoryStorage() : new CookieStorage(),
      storageKey: COOKIE_ACCESS_TOKEN // e.g. feathers-jwt
    })
  );

export default feathersClient;

// Setup feathers-vuex
const {
  makeServicePlugin,
  makeAuthPlugin,
  BaseModel,
  models,
  clients,
  FeathersVuex
} = feathersVuex(feathersClient, {
  serverAlias: 'api', // or whatever that makes sense for your project
  idField: 'id' // `id` and `_id` are both supported, so this is only necessary if you're using something else.
});

export {
  makeAuthPlugin,
  makeServicePlugin,
  BaseModel,
  models,
  clients,
  FeathersVuex
};

// src/store/index.js
// updated to use Nuxt module syntax for vuex (not classic mode)
import { initAuth } from 'feathers-vuex';
import { COOKIE_ACCESS_TOKEN } from '../modules/auth/persistence/constants';
import auth from './auth';

const requireModule = require.context(
  // The path where the service modules live
  '../services',
  // Whether to look in subfolders
  false,
  // Only include .js files (prevents duplicate imports`)
  /.js$/
);

const servicePlugins = requireModule
  .keys()
  .map((modulePath) => requireModule(modulePath).default);

export const plugins = [...servicePlugins, auth];
export const actions = {
  nuxtServerInit({ commit, dispatch, state }, { req }) {
    return initAuth({
      commit,
      dispatch,
      req,
      moduleName: 'auth',
      cookieName: COOKIE_ACCESS_TOKEN
    }).then(async () => {
      if (state.auth.accessToken) {
        // auth the current user from the JWT token obtained above
        await dispatch('auth/authenticate', {
          accessToken: state.auth.accessToken,
          strategy: 'jwt'
        });
      }
    });
  }
};

// /src/store/auth/index.js
import { makeAuthPlugin } from '../../feathers-client';

export default makeAuthPlugin({
  userService: 'users', // specify the userService to automatically populate the user upon login.
  state: {
    entityIdField: 'id', // The property in the payload storing the user id
    responseEntityField: 'user' // The property in the payload storing the user
  }
});

<script>
// src/pages/test/index.vue
import { models } from 'feathers-vuex';
export default {
  async fetch ({ store, params }) {
    try {
      // an example of an authenticated SSR call
     // will work so long as the user is authenticated
      const { User } = models.api;
       const res = await User.get(store.state.auth.user.id);
    } catch (e) {
      // will fail if the user is not authenticated
    }
  }
}
</script>
0reactions
jd1378commented, May 20, 2020

For anyone who is reading this later, I found a short chat going on featherjs slack and a response from marshall that confirms it that doing auth on SSR has problems currently: just checking here for a sanity check. We just found that calling auth/authenticate inside nuxtServerInit whilst nuxt is in production mode will cause the nuxt SSR to cache the response or something, then issuing users with incorrect JWTs. (thankfully we caught this before it got to production) So I think that the best solution is going for client side auth for now.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Developers - authenticated calls on the server do not work -
Nuxt CookieStorage example - authenticated calls on the server do not work.
Read more >
options - nuxt auth docs
Auth tokens are stored in various storage providers (cookie, localStorage, vuex) on user login to provide a seamless auth experience across ...
Read more >
Cookie is lost(thrown to logged out state) when killing/closing ...
I have built an web application using Nuxt.js. I have also added PWA features with ... This is working on local machine but...
Read more >
Authentication best practices for Vue - Sqreen Blog
Good practice: pass the login credentials in the request body, not in the URL. The reason behind it is that servers might log...
Read more >
Nuxt | FeathersVuex
In feathers-vuex@2.x , you can get access to the $FeathersVuex object by ... is called, it will have the JWT from the req...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found