rethinkdb / horizon

Horizon is a realtime, open-source backend for JavaScript apps.
MIT License
6.78k stars 349 forks source link

Proposal: Polished Authentication Strategy API #428

Open marshall007 opened 8 years ago

marshall007 commented 8 years ago

Currently, attaching third-party authentication providers requires a fairly deep understanding of the server internals. The goals of this proposal are as follows:

  1. Expose a public API for attaching third-party providers.
  2. API should encapsulate as much OAuth handshake logic as possible.
  3. API should work for built-in providers and totally custom ones.
const strategy = horizon.auth.strategy(<provider>) // -> Strategy
horizon.auth.use(strategy(<options>))

With this API in mind, implementing the Twitter OAuth provider from scratch becomes as simple as:

// create new strategy
const twitter = horizon.auth.strategy({
  name: 'twitter',
  protocol: 'oauth',
  temporary: 'https://api.twitter.com/oauth/request_token',
  auth: 'https://api.twitter.com/oauth/authenticate',
  token: 'https://api.twitter.com/oauth/access_token',
  profile: (credentials, params, get) =>
    get('https://api.twitter.com/1.1/account/verify_credentials.json').then(res => res.id)
});

// use this strategy
horizon.auth.use(twitter({
  consumer_key: '...',
  consumer_secret: '...'
}))
ericb commented 8 years ago

This looks great for oauth/oauth2, but what about custom authentication? From what I can tell, it looks as though currently each auth provider module really boils down to generating a user + redirecting with a JWT.

This could be be more or less enforced through documentation / interfaces, and with the addition of a way to register custom auth modules, we could effectively implement any kind of authentication.

Some pseudo code to illustrate:

// custom auth module

function customAuth(horizon, raw_options) {
  // do custom auth
  // ...

  // now generate token
  horizon._auth.generate(provider, user_id)

  // return jwt
}

module.exports = customAuth
// horizon server setup
const customAuth = require('../auth/custom'); 
const horizon_server = horizon(http_server, options);
horizon_server.registerAuthModule(customAuth);
stellanhaglund commented 8 years ago

Im with @ericb

I tried this out a little today and got pretty far by adding a new authType called 'custom' in the client and in the handshake on the server, note that i only use custom endpoints.

The above example looks like a good way of solving it.

Is there any other plans for this yet? If not i can give it a try and you can see if there is something you want to keep from that. I will need some kind of custom auth anyway.

Ping @deontologician

deontologician commented 8 years ago

This is medium term, so not necessarily the next release. Before taking a crack at it @stellanhaglund, we might want to get a comment from @Tryneus on the api design. My understanding of the auth classes right now is fuzzy, so I can't provide too much useful commentary.

dalanmiller commented 8 years ago

@marshall007 Is this in addition to having a supported set of OAuth providers? I like this API but I think it's too much boilerplate for developers who want to just add Google, Github, Twitter, etc to their application.

marshall007 commented 8 years ago

@dalanmiller yea so the idea is that we would use this API internally to pre-configure the default set of supported auth strategies. You would only have to use the horizon.auth.strategy() API if you needed support for something else.

Server would initialize built-in strategies automatically:

horizon.auth.twitter = horizon.auth.strategy(...)
horizon.auth.github = horizon.auth.strategy(...)
// ...

Which get consumed by the user in one of the following ways:

## 1 - config
[auth.github]
id = "your_client_id"
secret = "your_client_secret"
// 2 - programmatic - built-in
horizon.auth.use(horizon.auth.twitter({ ... }))
// 3 - programmatic - custom
const SlackStrategy = horizon.auth.strategy({ name: 'slack', ... })
horizon.auth.use(SlackStrategy({ ... }))
marshall007 commented 8 years ago

Also note that while I only illustrated options specifically related to OAuth strategies, the idea is that protocol could be a variety of things (anonymous, unauthenticated, http-basic, etc) and hopefully this API would allow you to configure arbitrary authentication workflows. I just haven't had time to think through all that yet.

stellanhaglund commented 8 years ago

here is a pr for @ericb's suggestion.

Right now it goes completely outside the default auth, allowing you to do whatever you want starting from the handshake this is beacause I want to use other than the internal tables for my users, but you could use those if you wanted to.

https://github.com/rethinkdb/horizon/pull/506

deontologician commented 8 years ago

I think #64 is going to make this easier.