feathersjs-ecosystem / feathers-redux

Integrate Feathers with your Redux store
MIT License
114 stars 23 forks source link

Make authenticated API calls #46

Open amaury1093 opened 6 years ago

amaury1093 commented 6 years ago

Steps to reproduce

After authentication, I store the accessToken in my redux state (and after that redux-persist automatically stores it in localStorage).

What I want to achieve is: Make every feathers API call with the accessToken, if it's present in the redux state.

What I'm doing now:

export const app = feathers(); // + configure rest, hooks...

// Append accessToken in header if present.
// It would be better to take the accessToken from redux state,
// but for now we keep this solution (i.e. take directly from
// localStorage)
app.hooks({
  before: hook => {
    const item = window.localStorage.getItem('persist:myApp'); // This is where redux-persist saves the accessToken
    if (item) {
      const { auth } = JSON.parse(item);
      const { accessToken } = JSON.parse(auth);
      if (!accessToken) {
        return;
      }
      hook.params.headers = {
        Authorization: `Bearer ${accessToken}`,
        ...hook.params.headers
      };
      return hook;
    }
  }
});

So what I'm doing is, before each API request, retrieve the accessToken from the localStorage (and not from the in-memory store!).

Expected behavior

There should surely be a way to do this without retrieving localStorage and parsing JSON before each api request.

Actual behavior

Didn't find a clean way to pass state variables to the feathers app.

The only way I found is, on every api request, do

componentWillMount() {
  this.props.findUsers({ // findUsers dispatches feathersRedux.users.find
    headers: { Authorization: `Bearer ${this.props.auth.accessToken}` }
  })
}

But doing this on every method is not viable.

System configuration

Tell us about the applicable parts of your setup.

Module versions 2.1.0

NodeJS version: Not relevant

Operating System: Not relevant

Browser Version: Not relevant

React Native Version: Not relevant

Module Loader: Not relevant

chris-garrett commented 6 years ago

I just double checked my project(s) and I am using @feathersjs/authentication-client for this.

https://docs.feathersjs.com/api/authentication/client.html https://github.com/feathersjs/authentication-client

My app looks like:

...
import authentication from 'feathers-authentication-client';
...
export const app = feathers()
  .configure(feathers.socketio(socket))
  .configure(feathers.hooks())
  .configure(authentication({
    storage: window.localStorage, // store the token in localStorage and initially sign in with that
  }));

In my oauth callback page I end up calling:

app.authenticate({strategy: 'jwt', accessToken: Cookies.get('feathers-jwt')}));

at this point the client will exchange the cookie for an auth token and stuff it in local storge. After this all subsequent calls should be authenticated.

crypto-titan commented 6 years ago

Hey @chris-garrett,

I'm a beginner and I'm trying to implement this for the past 6 hours or so, I am having difficulties because app.authenticate() is a promise and not sure how to wait it out. I have tried something like this:

    import React from 'react';
    import Dashboard from './dashboard';
    import PropTypes from 'prop-types';
    import feathers from '@feathersjs/feathers';
    import auth from '@feathersjs/authentication-client';
    import socketio from '@feathersjs/socketio-client';
    import io from 'socket.io-client';
    import { Provider } from 'react-redux';
    import Realtime from 'feathers-offline-realtime';
    import reduxifyServices, { getServicesStatus } from 'feathers-redux';
    import configureStore from '../../store';

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

    class Layout extends React.Component {
        async componentDidMount(){
            await app.authenticate();
        }

        render() {
            const services = reduxifyServices(app, ['users', 'customers']);
            const store = configureStore(services);
            store.dispatch(services.customers.create({ text: 'message 1' }));

            return (
                <div>
                <Provider store={store}>
                <Dashboard services={services} getServicesStatus={getServicesStatus}>
                    { this.props.children }
                </Dashboard>
                </Provider>
                </div>
                );
        }
    }

    export { Layout };

But it does not seem to work, can you give me some advice? :)

eddyystop commented 6 years ago

First you can look at eddyystop/feathers-reduxify-authentication. Its a wrapper over app.authenticate() while maintaining an 'authorized' prop in the state.

Your client will have to be in a 'waiting for auth' state until the promise resolves using app.authenticate() or until the authication state is set using feathers-reduxify-authentication. That can be awkward. You can look at (the by now quite outdated) eddyystop/feathers-starter-react-redux-login-roles for a redux example.

You can always use a pollyfill for async/await in the client to make it sync, but your frontend will likely be 'hung' until auth is done.