feathersjs / feathers

The API and real-time application framework
https://feathersjs.com
MIT License
15.06k stars 752 forks source link

Client keeps making authenticated calls after logging out #2557

Closed martigasco closed 2 years ago

martigasco commented 2 years ago

Steps to reproduce

I noticed this behaviour in a complex application (React Native with web support), but it's easily reproduced in the node console, using Feathers 4.5.12: const feathers = require("@feathersjs/feathers"); const socketio = require("@feathersjs/socketio-client"); const auth = require("@feathersjs/authentication-client"); const io = require("socket.io-client");

const socket = io("http://localhost:3030/", {
  transports: ["websocket"],
  forceNew: true
});

const client = feathers();
client.configure(socketio(socket));

client.configure(
  auth({
    path: "/authentication/users",
    storageKey: `appName_jwt`,
  })
);

// Client logs in successfully
client.authenticate({strategy: "local", email: "mail@mail.com", password: "123456",});

// Client makes an authenticated call
client.service("users").find();

// Client logs out
client.logout();

// Client makes an authenticated call successfully, even though logged out
client.service("users").find();

All the steps beginning from authenticate are obviously made one by one, considering they are async, and all of them succeed. In fact, the moment logout is called the jwt token is in fact removed from storage, but somehow the client has it cached for subsequent calls.

In the web version of this app (it's a React Native with web implementation) reloading the page solves the issue, but that is in fact opening a brand new connection, so not a real solution.

Expected behavior

The client instance should stop making authenticated calls after logging out.

Actual behavior

The client includes jwt token in every call, even though it logged out successfully and client.authentication.authenticated is false. If I log the server's params in a hook of the request made after logging out, the params include:

authentication: {
  strategy: 'jwt',
  accessToken: '**********'
},

System configuration

Tell us about the applicable parts of your setup.

Module versions (especially the part that's not working):

"@feathersjs/authentication-client": "^4.5.12",
"@feathersjs/feathers": "^4.5.12",
"@feathersjs/socketio-client": "^4.5.12",

NodeJS version: v16.13.1

Operating System: MacOS BigSur (but happens with production server as well)

Browser Version: Chrome 98.0.4758.80 (but happens in mobile and console as well)

React Native Version: V0.63.3 (but happens in web and console as well)

Module Loader:

daffl commented 2 years ago

The likely reason is that some components that are making calls are not waiting until the promise returned by client.logout() to resolve as shown in the test that is verifying that what you are describing is working at https://github.com/feathersjs/feathers/blob/dove/packages/authentication-client/test/integration/commons.ts#L92.

martigasco commented 2 years ago

Well that would make sense! Problem is, it's not just after logging out this happens. I did even wait for minutes after the logout promise resolved, checked storage to see that the token was gone, and initiating a new request after all that, it still carries the token, as if it had it cached somewhere...

Could it have something to do to the fact i'm using socket.io and the integration test uses another transport?

daffl commented 2 years ago

The other thing to watch out for is to handle when the logout promise errors. I have a feeling you are getting an error and not handling it either using async/await with await client.logout() or client.logout().catch(error => console.error(error)).

martigasco commented 2 years ago

Hi! I already considered that, but as you can see, in this test I did from the node console, the logout promise resolves successfully, and after that I make a request to the api (the console on the right), and in the api I console.log the context.params, which clearly show that credentials are still being sent.

Screenshot 2022-02-16 at 10 56 02

One thing that's true is that if I do the same test with another api with the same Feathers version, it doesn't happen, and the only difference seems to be that in this case, the authentication service has a custom name (authentication/users), because I have another one for admins (authentication/admins), like this:

  const userAuthentication = new AuthenticationService(app, 'userAuthentication');
  userAuthentication.register('jwt', new LongJWTStrategy());
  userAuthentication.register('local', new MyLocalStrategy());
  userAuthentication.register('anonymous', new AnonymousStrategy());
  userAuthentication.register('google', new GoogleStrategy());
  app.use('/authentication/users', userAuthentication);

  const adminAuthentication = new AuthenticationService(app, 'adminAuthentication');
  adminAuthentication.register('jwt', new JWTStrategy());
  adminAuthentication.register('local', new LocalStrategy());
  adminAuthentication.register('anonymous', new AnonymousStrategy());
  app.use('/authentication/admins', adminAuthentication);

And when configuring the client auth:

client.configure(
  auth({
    path: "/authentication/users",
    storageKey: `ubelong_fulrl_jwt`,
  })
);

Could that be the origin of the bug? That the logout process somehow omits the "path" configuration at some stage?

daffl commented 2 years ago

I'm not sure, generally I wouldn't expect login to work properly then either. If this is an actual issue, it should be possible to create a more minimal reproducible example, e.g. based on https://github.com/feathersjs/feathers-chat that can illustrate your problem.