Feathers-vuex: Support of feathers 4.0 crow authentication changes

Created on 16 Jul 2019  路  10Comments  路  Source: feathersjs-ecosystem/feathers-vuex

Support of feathers crow authentication changes

The feathersClient will get some internal updates to the authentication part with crow.
How will the new version be supported?

I tried to already use it, but there are some calls to old functions like:

return feathersClient.passport
          .verifyJWT(response.accessToken)
          .then(function(payload) {
            commit('setPayload', payload)

As feathers is now longer working with passport this part is failing.

Possible fix

Use something like const jwt = await feathersClient.getAccessToken() and commit its result to the store.

System configuration

    "@feathersjs/authentication-client": "^^4.3.0-pre.1",
    "@feathersjs/feathers": "^^4.3.0-pre.1",
    "feathers-vuex": "^2.0.0-pre.62"
    "vue": "^2.6.10",
    "vuex": "^3.1.0",
Fixed in next release major

Most helpful comment

Here's what I did. When calling makeAuthPlugin() I overrode actions.responseHandler() with my own custom function. I'm using Auth0 to authenticate on the client side and not using feathers to generate an access token. I needed to add a few properties and custom logic to the auth plugin. My implementation ends up looking something like:

// src/store/services/auth.js
import { feathersClient, makeAuthPlugin, models } from '@/services/api'

export default makeAuthPlugin({
  userService: 'users',
  state: {
    my_custom_prop: null,
    ...
  },
  actions: {
    responseHandler: async ({ commit, state }, response) => {
      // my custom implementation
    }
  },
  mutations: { ... },
  getters: { ... }
}

I didn't need to verify or decode the token returned from feathers (which is exactly the same one I sent them from the client) and the new JWTStrategy already sends you back a decoded token by default (I think it's in response.authentication.payload).

There's probably a lot of stuff that's only relevant to my particular app, but if you're interested, you can take a look at my implementation here

All 10 comments

This is one of two things that are required before I can ship 2.0. Any information you can add about your findings will be greatly appreciated.

Here's what I did. When calling makeAuthPlugin() I overrode actions.responseHandler() with my own custom function. I'm using Auth0 to authenticate on the client side and not using feathers to generate an access token. I needed to add a few properties and custom logic to the auth plugin. My implementation ends up looking something like:

// src/store/services/auth.js
import { feathersClient, makeAuthPlugin, models } from '@/services/api'

export default makeAuthPlugin({
  userService: 'users',
  state: {
    my_custom_prop: null,
    ...
  },
  actions: {
    responseHandler: async ({ commit, state }, response) => {
      // my custom implementation
    }
  },
  mutations: { ... },
  getters: { ... }
}

I didn't need to verify or decode the token returned from feathers (which is exactly the same one I sent them from the client) and the new JWTStrategy already sends you back a decoded token by default (I think it's in response.authentication.payload).

There's probably a lot of stuff that's only relevant to my particular app, but if you're interested, you can take a look at my implementation here

feathersClient.getAccessToken is not a function
is what I get when I try to use this method on feathers-vuex auth-module-actions.js in order to replace passport undefined call.

I'm using on my frontend

"@feathersjs/authentication-client": "^^4.3.0-pre.2",
"@feathersjs/feathers": "^^4.3.0-pre.2",
"feathers-vuex": "^2.0.0-pre.69"

and I updated my backend as well to same @feathersjs/* version

Documented function is verifyAccessToken(accessToken)

authService.verifyAccessToken(accessToken, [options, secret]) -> Promise verifies the access token. By default it will try to verify a JWT using configuration.jwtOptions merged with options (optional). Will either use configuration.secret or the optional secret to verify the JWT. Returns the encoded payload or throws an error.

Same outcome, it's undefined on feathersClient object

I checked the same variable after initialization in my frontend after theese lines

const feathersClient = feathers()
  .configure(socketio(socket))
  .configure(auth({ storage: localStorage }));

using local strategy for login

same outcome...

neither feathersClient.get('defaultAuthentication') works as documented

I can't figure this out, I'm sorry.

My current workaround is based on the suggestion by @morphatic.

    "@feathersjs/authentication-client": "^4.3.0-pre.2",
    "@feathersjs/feathers": "^4.3.0-pre.2",
    "@feathersjs/socketio-client": "^4.3.0-pre.2",
    "feathers-vuex": "^2.0.0-pre.69",
// src/store/services/auth.js
import { makeAuthPlugin } from '@/utils/feathers-client';

export default makeAuthPlugin({
  userService: 'users',
  state: {
    accessToken: localStorage.getItem('feathers-jwt') || null,
  },

  getters: {
    isAuthenticated: state => !!state.accessToken,
  },

  mutations: {
    setAccessToken(state, accessToken) {
      state.accessToken = accessToken;
      localStorage.setItem('feathers-jwt', accessToken);
    },
  },

  actions: {
    responseHandler({ commit }, response) {
      if (response.accessToken) {
        commit('setAccessToken', response.accessToken);
        // Decode the token and set the payload, but return the response
        // Populate the user if the userService was provided
        // if (state.userService && payload.hasOwnProperty(state.entityIdField)) {
        //   return dispatch('populateUser', payload[state.entityIdField]).then(() => {
        //     commit('unsetAuthenticatePending');
        //     return response;
        //   });
        // }
        commit('unsetAuthenticatePending');
        return response;

        // If there was not an accessToken in the response,
        // allow the response to pass through to handle two-factor-auth
      }
      return response;
    },
  },
});

In this workaround you're giving up the payload and the user object population... you keep only the JWT Token and nothing else... are you doing the decoding and user details retreival in your vue components?

So... basically, it is my understanding that from feathersClient you can't have access in any way at the AuthenticationService as defined in docs and in feathers backend app object.

Please, correct me if I'm wrong.

@marshallswain what is your intent with your library code? Keep current support to buzzard and make possible any kind of support to crow libs if feathersClient.passport is undefined? Or upgrade everything to crow, dropping passport calls completely?

This is my complete workaround defined in my auth.ts file

// src/services/auth.ts
import { makeAuthPlugin } from '../feathers-client';
import decode from 'jwt-decode';
import { models } from '../feathers-client';

function getValidPayloadFromToken(token: any): any {
  if (token) {
    try {
      const payload = decode(token);
      return payloadIsValid(payload) ? payload : undefined;
    } catch (error) {
      return undefined;
    }
  }
  return undefined;
}

export function payloadIsValid(payload: any): boolean {
  return payload && payload.exp * 1000 > new Date().getTime();
}

export default makeAuthPlugin({
  serverAlias: 'xxxxxxxxxx',
  userService: 'users',
  actions: {
    responseHandler({ commit, state, dispatch }: any, response: any) {
      if (response.accessToken) {
        commit('setAccessToken', response.accessToken);

        const payload = getValidPayloadFromToken(response.accessToken);
        if (!payload) {
          return response;
        }

        commit('setPayload', payload);

        let user = response[state.responseEntityField];

        // If a user was returned in the authenticate response, use that user.
        if (user) {
          if (state.serverAlias && state.userService) {
            const Model = Object.keys(models[state.serverAlias])
              .map((modelName) => models[state.serverAlias][modelName])
              .find((model) => model.servicePath === state.userService);
            if (Model) {
              user = new Model(user);
            }
          }
          commit('setUser', user);
          commit('unsetAuthenticatePending');
          // Populate the user if the userService was provided
        } else if (state.userService && payload.hasOwnProperty(state.entityIdField)) {
          return dispatch('populateUser', payload[state.entityIdField]).then(() => {
            commit('unsetAuthenticatePending');
            return response;
          });
        } else {
          commit('unsetAuthenticatePending');
        }
        return response;
      } else {
        // If there was not an accessToken in the response, allow the response to pass through to handle two-factor-auth
        return response;
      }
    },
  },
});

This is my complete workaround defined in my auth.ts file

// src/services/auth.ts
import { makeAuthPlugin } from '../feathers-client';
import decode from 'jwt-decode';
import { models } from '../feathers-client';

function getValidPayloadFromToken(token: any): any {
  if (token) {
    try {
      const payload = decode(token);
      return payloadIsValid(payload) ? payload : undefined;
    } catch (error) {
      return undefined;
    }
  }
  return undefined;
}

export function payloadIsValid(payload: any): boolean {
  return payload && payload.exp * 1000 > new Date().getTime();
}

export default makeAuthPlugin({
  serverAlias: 'xxxxxxxxxx',
  userService: 'users',
  actions: {
    responseHandler({ commit, state, dispatch }: any, response: any) {
      if (response.accessToken) {
        commit('setAccessToken', response.accessToken);

        const payload = getValidPayloadFromToken(response.accessToken);
        if (!payload) {
          return response;
        }

        commit('setPayload', payload);

        let user = response[state.responseEntityField];

        // If a user was returned in the authenticate response, use that user.
        if (user) {
          if (state.serverAlias && state.userService) {
            const Model = Object.keys(models[state.serverAlias])
              .map((modelName) => models[state.serverAlias][modelName])
              .find((model) => model.servicePath === state.userService);
            if (Model) {
              user = new Model(user);
            }
          }
          commit('setUser', user);
          commit('unsetAuthenticatePending');
          // Populate the user if the userService was provided
        } else if (state.userService && payload.hasOwnProperty(state.entityIdField)) {
          return dispatch('populateUser', payload[state.entityIdField]).then(() => {
            commit('unsetAuthenticatePending');
            return response;
          });
        } else {
          commit('unsetAuthenticatePending');
        }
        return response;
      } else {
        // If there was not an accessToken in the response, allow the response to pass through to handle two-factor-auth
        return response;
      }
    },
  },
});

+1 it works for me

This is my complete workaround defined in my auth.ts file

Could you please point out HOW to get this running? I don't have a makeAuthPlugin, is this purely based on the v2.0 TS branch? (https://github.com/feathers-plus/feathers-vuex/tree/typescript)

@chris-aeviator Yes. This is all based on the 2.0 version. I do not have the availability to manage both versions. Check out the PR, here on how to get up and running with 2.0. It is a MUCH better experience than the previous version.

I've implemented support for [email protected] authentication in [email protected]. Since the new Feathers auth plugin (on the server) returns the user by default, it allowed me to simplify the responseHandler and remove some unneeded code.

I've added very brief docs to #216 (at bottom of the first comment) on how to restore compatibility with previous versions of Feathers.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Hiws picture Hiws  路  3Comments

daenuprobst picture daenuprobst  路  5Comments

gustojs picture gustojs  路  7Comments

RubyRubenstahl picture RubyRubenstahl  路  3Comments

apmcodes picture apmcodes  路  6Comments