Skip to content
This repository has been archived by the owner on Apr 23, 2019. It is now read-only.

No Auth header added when sending 1st request #80

Closed
Albert-Gao opened this issue Nov 11, 2017 · 10 comments
Closed

No Auth header added when sending 1st request #80

Albert-Gao opened this issue Nov 11, 2017 · 10 comments

Comments

@Albert-Gao
Copy link

Albert-Gao commented Nov 11, 2017

Steps to reproduce

I have a service which has a authenticate('jwt') at its before.all() hook.

In my React app, in the componentDidMount I send a request to this endpoint from feathers client, swapService.find(), it gives me No Auth token error(401), it's weird because there are tokens both at cookie and local storage.

Put this aside, when the user press Refresh button, the component will send a request using the same code before, this time it could get the expected result without any problem….

This is the feathers-client settings:

import axios from 'axios';
import auth from 'feathers-authentication-client';
import hooks from 'feathers-hooks';
import rest from 'feathers-rest/client';
import feathers from 'feathers/client';

const authConfig = {
  header: 'Authorization',
  path: '/authentication',
  jwtStrategy: 'jwt',
  entity: 'user',
  service: 'users',
  cookie: 'feathers-jwt',
  storageKey: 'feathers-jwt',
  storage: window.localStorage
};

const client = feathers()
  .configure(hooks())
  .configure(rest().axios(axios))
  .configure(auth(authConfig));

const swapService = client.service('swap');

export default client;
export {swapService};

This is the redux action for handling the request thing:

import { swapService } from '../utils/client';

  LoadList: () => async dispatch => {
    dispatch(actions.LoadListRequested());
    try {
      const swaps = await swapService.find();
      dispatch(actions.LoadListSuccessfully(swaps));
    } catch (error) {
      dispatch(actions.LoadListFailed(error));
    }
  }

When the client starts, the 1st request will be sent:

  componentDidMount() {
    this.props.loadList();
  }

And this this.props.loadList() will be invoked again when the user press the refresh button (Which will be shown when there is an error like this no auth token.).

It will work if I add this:

Got this after I read #48

client.hooks({
  before: {
    all: [
      hook => {
        const apiKey = window.localStorage[authConfig.storageKey];
        if (apiKey) {
          hook.params.headers = { [authConfig.header]: `${apiKey}` };
        }
      }
    ]
  }
});

Is this a bug or I setup something wrongly? Glad to help if it's a bug. :)

Expected behavior

Consider the token is just there, the 1st request from componentDidMount should get the list.

Actual behavior

The 1st one always fails while the 2nd always succeed.

System configuration

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

NodeJS version:
Node 8.9.0

Operating System:
OS X 10.13.1

Browser Version:
Chrome 62.0.3202

Module Loader:
latest create-react-app

@Albert-Gao Albert-Gao changed the title No Auth header added when first send the request No Auth header added when sending 1st request Nov 11, 2017
@daffl
Copy link
Member

daffl commented Nov 11, 2017

You have to call app.authenticate() with no parameters and wait for the promise it returns to resolve.

@Albert-Gao
Copy link
Author

Albert-Gao commented Nov 11, 2017

Thanks for the reply. Got confused by the docs here, for this authenticate() method, it said:

Authenticate with a Feathers server by passing a strategy and other properties as credentials.

Sounds like it will send a request to validate the identity? (in my case, jwt)

After reading the source code, as you said, if no option is given, it just returns the jwt.

so, maybe a better way to do this is in the client side hooks? Like a setup. Such that, later on, we can just call the service.

And, I think the doc should add the proper steps for calling an auth-needed API?

but why it works the 2nd time?.....
Furthermore, for every request which requires auth, I need to resolve this authenticate() first?

@Albert-Gao
Copy link
Author

Albert-Gao commented Nov 11, 2017

So, following my previous code snippet, I add the authenticate() call.

const result = await client.authenticate();
console.log('result-->', result);
const swaps = await swapService.find();

Where the client and swapService are still the same:

const client = feathers()
  .configure(hooks())
  .configure(rest().axios(axios))
  .configure(auth(authConfig));

const swapService = client.service('swap');

The problem is the 2nd line never gets executed...Anything I did wrong here?

The example from the doc shows how to grab the value from the JWT and then use it as a parameter when calling the service, what I want is to add the auth header to the HTTP request.

Rather than doing this by myself, like

client.hooks({
  before: {
    all: [
      hook => {
        const apiKey = window.localStorage[authConfig.storageKey];
        if (apiKey) {
          hook.params.headers = { [authConfig.header]: `${apiKey}` };
        }
      }
    ]
  }
});

What's the feathers way to do an auth call to the API with the feathers-client?

Or is there a client-side service level hook that I can add header only to the service I want.

@daffl
Copy link
Member

daffl commented Nov 11, 2017

That probably means you are getting an error.

try {
  const result = await client.authenticate();
  console.log('result-->', result);
  const swaps = await swapService.find();
} catch(e) {
  console.error(e);
}

@Albert-Gao
Copy link
Author

You are right. I forget to turn of the mongodb...... :D And there is no error message for this.

Now it works.

But it will send an extra request, when is the proper timing to do this?
Do I need to call the authenticate() method only once just after I moving the jwt from cookie to localstorage?

I think I don't need to call the authenticate() every time I initialize the page, right? Sounds overkill to me... Because the server will handle the expired case anyway.

@daffl
Copy link
Member

daffl commented Nov 11, 2017

You do. This is to handle socket and HTTP authentication the same way and to explicitly being able to deal with expiration and login error separately. All Feathers chat frontends (e.g. https://github.com/feathersjs/feathers-chat/blob/master/public/vanilla/app.js) show how it should be used.

@Albert-Gao
Copy link
Author

Understood. But seems it should give the rest way a little bit flexibility. :)

And why the 2nd request could have the auth header, isn't this an inconsistent behavior?

@Albert-Gao
Copy link
Author

I added the client.authenticate() method to the componentDidMount of my entry App component in order to do the auth setup.

But it seems that it won't work unless I call the authenticate() just before calling the service like this:

await client.authenticate();
const swaps = swapService.find();   

Which looks like I need to do this every time I want to send a request?

Besides this,
If I add the auth header manually, the server log is this:

info: after: users - Method: get
info: after: swap - Method: find

If I call the authenticate() method() first, the server log will be:

info: after: users - Method: get
info: after: authentication - Method: create
info: after: users - Method: get
info: after: swap - Method: find

Which means a separate request has been sent, and it seems that it refreshes the jwt.

And if we gonna do it every time before we sending a request, looks like not that good.

What do you think? :) Or I did something wrong?

@daffl
Copy link
Member

daffl commented Nov 13, 2017

It only needs to be done once when the page is initialized. Have a look at how the React chat application does it (here)

@Albert-Gao
Copy link
Author

Thanks for the answer.
I check that code, the authenticate() has been added to the same place: componentDidMount of the parent component.

It seems that In my code, according to my console.log, when the client.authenticate() in the parent component gets resolved, the child component is already mounted and send that request, that's why it doesn't work until I put them together via two await because now they happen step by step.

Maybe websocket's faster speed causes it to behave differently here. I use rest here.

@daffl daffl closed this as completed Dec 18, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants