Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication next #1045

Closed
daffl opened this issue Oct 6, 2018 · 10 comments
Closed

Authentication next #1045

daffl opened this issue Oct 6, 2018 · 10 comments

Comments

@daffl
Copy link
Member

daffl commented Oct 6, 2018

I wrote about this a little already in the Feathers Crow roadmap update. This issue is meant to collect all related issues that the next version of Feathers authentication will cover.

Framework independent

With the current (Buzzard) release, Feathers core became fully framework independent, however, the authentication plugin is still registering some Express middleware. In the next version that middleware will move into @feathersjs/express and make authentication core also framework independent. This will allow new transports like Koa or MQTT.

Improved socket authentication

Currently, websockets are authenticated and logged out through a custom event mechanism. This was causing some issues (e.g. infrequent “No auth token” errors), especially around reliable reconnection on both sides, the server and the client.

Improved authentication client

This will remove dependence on the stateful access token and gracefully handle socket re-authentication.

No more cookies, improved oAuth

Feathers is a library for creating APIs using JWT for authentication. The one instance where a normal Feathers setup uses cookies is to pass the JWT to the browser after an oAuth (Facebook, Google etc.) authentication flow. The new oAuth and authentication client will no longer use cookies and split the oAuth flow into two parts instead of trying to do everything at once. The oAuth access token will be set in a cross-domain friendly (only locally accessible) URL hash which can then be exchanged for a JWT using Feathers standard authentication mechanism.

Better option handling

All authentication settings will now be evaluated at runtime so it is possible to set them dynamically and there will be no errors for options that are not required. It will also allow to pass a custom authentication service.

Refresh tokens and blacklisting

Instead of allowing refreshing with the existing JWT the standard authentication service will now also return a longer lived refresh token. Token blacklisting still needs to be implemented manually but will be easier to integrate through the methods in the new authentication service.

@daffl
Copy link
Member Author

daffl commented Jan 23, 2019

Deprecate Passport

This discussion started in #844 and in #844 (comment) summarizes the points why this is happening. In short, there has not been much development in PassportJS especially around supporting frameworks other than Express and most other HTTP frameworks like Koa or Hapi moved on to use more flexible and minimalist authentication libraries (like https://github.com/simov/grant for oAuth). It also turned out that there are only four types of strategies that are really needed:

  • Local (username/password)
  • JWT
  • oAuth (+ oAuth access token)
  • API key

Without having to work around Passport, Feathers can easily sit on top of any HTTP library and any other transport mechanism (like MQTT or even P2P connections) with a clear separation of concerns and provide an authentication mechanism that is much easier to understand and customize.

Using params.authentication

On the Feathers side, setting params.authentication in a service call will be the only way to provide authentication information. params.authentication will contain the information necessary to authenticate the service call and will be in the format of e.g. { strategy: 'local', email: 'email', password: 'password' } that is already being used:

// Call `find` with a given JWT
app.service('messages').find({
  authentication: {
    strategy: 'jwt',
    accessToken: 'someJWT'
  }
});

The caller (like a REST or websocket transport, a hook or a unit test) will be responsible for passing params.authentication. This means there will be no more confusion if the authenticate hook runs for internal calls or not or where the authentication information is actually coming from.

The authenticate hook

The authenticate hook will continue to exist. It will take a list of strategy names and will either

  • Continue to the next hook with the first strategy that didn't throw an error and merge its return value (see below) into params
  • Or throw the error of the first strategy that failed if all strategies failed

If params.authentication contains a strategy name only that strategy will be called. Otherwise all strategies will be called. If params.authentication is not set at all, the hook will

  • Throw an error for external calls
  • Continue as usual for internal calls (e.g. when setting params.user manually)
app.service('messages').hooks({
  before: authenticate('jwt', 'local', 'anonymous')
});

Authentication strategies

A basic authentication strategy is an object with an authenticate method that gets the data of params.authentication and returns a success object or throws an error if it was not successful:

const { Forbidden } = require('@feathersjs/errors');

const daveStrategy = {
  async authenticate (authParams) {
    const { username, password } = authParams;

    if (username === 'david' && password === 'secret') {
      return {
        user: {
          name: 'Dave',
          admin: true
        }
      };
    }

    throw new Forbidden('Not super Dave');
  }
};

app.authentication.registerStrategy('superdave', daveStrategy);

In the authenticate hook, the returned object will be merged into the service call params so this example would set params.user.

Extending strategies

Authentication strategies can contain and call additional methods internally. Feathers strategies will be implemented as classes that can be customized through extension (this will replace the Verifiers and basically combines the strategy and the verifier into a single class):

class LocalStrategy {
  constructor(app);
  async findUser(authParams);
  async comparePassword(user, password);
  async authenticate (authParams);
}

class MyLocalStrategy extends LocalStrategy {
  async findUser(authParams);
}

app.authentication.registerStrategy('local', new MyLocalStrategy(app));

HTTP parsing

A strategy can also provide a parse method which will get a basic Node HTTP request and response and should return the value for params.authentication (or null):

const daveStrategy = {
  async authenticate (authParams) {
    throw new Forbidden('Not super Dave');
  }

  async parse (req, res) {
    const apiKey = req.headers['x-super-dave'];

    if (apiKey) {
      return {
        strategy: 'superdave',
        apiKey
      }
    }

    return null;
  }
};

HTTP libraries can then decide if and how they use those methods to set params.authentication or authenticate their own middleware.

app.authenticate

app.authenticate(authParams, [ strategies ]) will run the given strategies with the given authentication parameters:

// Will return the user for a JWT (on the server)
const { user } = app.authenticate({ accessToken }, 'jwt');

@jamesholcomb
Copy link

setting params.authentication in a service call will be the only way to provide authentication information.

Could you walk through the design points on why it was necessary to provide accessToken on every service() call?

@daffl
Copy link
Member Author

daffl commented Feb 21, 2019

This only applies to the server and is what implicitly happens already through params.provider and params.headers (in the case of websockets this are only fake headers) which are set for every service call once authentication is configured. This was breaking Feathers separation between transport protocol independent hooks and services and the actual transport mechanism (like HTTP with Express or Socket.io) and made it really confusing when e.g. an authenticate('jwt') hook actually runs and what it is using for its authentication information.

Usability wise nothing will really change for external or internal calls but if you now want to trigger the authenticate hook explicitly on the server, you can always set params.authentication. This is especially important for new framework plugins other than Express (e.g. Koa, HTTP2 or a messaging queue) which now have a standardized way to pass their authentication information to Feathers authentication mechanism.

The authentication client will still make authenticated requests once it has been logged in by calling app.authenticate().

@jamesholcomb
Copy link

Thanks for clarifying. Very much looking forward to testing with v3, particularly with socket.io React Native clients.

@daffl
Copy link
Member Author

daffl commented Feb 21, 2019

Will definitely put the information about prereleases here. Just wrapping up the authentication client which should handle authenticated websockets much more reliably. Once that is done it would be great to have some help testing the new core + local and jwt authentication. oAuth should be soon to follow after that.

@joacub
Copy link

joacub commented Mar 10, 2019

when version 3 comes out?

@daffl daffl changed the title Authentication v3 Authentication next Mar 10, 2019
daffl added a commit that referenced this issue Mar 10, 2019
BEAKING CHANGE: Express and passport independent rewrite of authentication core server. See #1045
@joacub
Copy link

joacub commented Mar 26, 2019

Any response to my question ?

@daffl
Copy link
Member Author

daffl commented Mar 26, 2019

You can see the current progress in the master branch and I will put out a blog post when beta testers can try it out. The status is:

Module Code Docs CLI
@feathersjs/authentication 100%
@feathersjs/authentication-local 80%
@featherjs/authentication-oauth 90%
@feathersjs/authentication-client 80%

@MichaelJCole
Copy link

MichaelJCole commented Apr 20, 2019

Hi, this is great!

Future flexibility is cool, but I don't have confidence with auth in my FeathersJS project yet and I'm going through a confusing process to create that.

Having a complete, tested, and secure auth implementation means I can confidently move on to features. One of the best reasons to use MeteorJS was it's excellent accounts integration all the way to the client.

Looking through FeatherJS issues, many are about authentication, so I'm excited for this work to land!

Where is the best documentation (or reference implementation) for a complete secure auth system (local auth, access control, emails, etc) in FeathersJS today?

This is what I have so far - is there anything better?

2018/02 - Setting up email verification in FeathersJS (rehash of 2017 article)
2018/02 - Repo from article above
2017/01 - How to setup email verification in FeathersJS (broken)
2018/06 - Vue Authentication with Feathersjs

Thanks in advance!

@daffl
Copy link
Member Author

daffl commented Jun 7, 2019

All related issues have now been closed and the prerelease for Feathers v4 with all the proposed changes implemented is available for testing. See the migration guide for more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants