hoodiehq / discussion

General discussions and questions about Hoodie
7 stars 1 forks source link

API for oAuth plugin #75

Open zoepage opened 9 years ago

zoepage commented 9 years ago

I'd love start the discussion on the oAuth plugin including scope, dependencies and API. What are your thoughts already?

How much will this plugin depend on the new https://github.com/hoodiehq/hoodie-account-server/tree/api?

repo: https://github.com/hoodiehq/hoodie-plugin-oAuth

docs: Github: https://developer.github.com/v3/oauth/ Twitter: https://dev.twitter.com/oauth/overview/introduction Facebook: https://developers.facebook.com/docs/facebook-login/login-flow-for-web/v2.4 Google: https://developers.google.com/identity/protocols/OAuth2

cc @janl @christophwitzko @espy @boennemann @gr2m

espy commented 9 years ago

I may be misremembering this, but I think what we discussed… 2 years ago 0_o was something like:

  1. install the plugin
  2. in the admin panel, select which auth providers you want via checkboxes, so the server can show you the callback endpoints
  3. register your app with the oauth providers and enter the endpoint you got from the admin panel
  4. in the frontend, do

    hoodie.account.signInWith('github').done(function(response){
     console.log('oauth token: ', response.token);
    });
  5. auth window pops up, user auths, promise gets fulfilled, client has auth token, can do requests with it.

That's what we want, right?

gr2m commented 9 years ago

Ideally, you wouldn't need to handle any tokens yourself.

hoodie.account.signInWith('github').done(function(username){
  console.log('Hey there, ', username)
})

And options can be passed as a second parameter, to request only a certain scope

hoodie.account.signInWith('github', {scopes: ['user:email']}).done(function(username){
  console.log('Hey there, ', username)
})
espy commented 9 years ago

Ok, but it definitely should be available in the user object in case the dev wants to do anything with it in the client, for whatever reason.

Anway, the two main contenders for hapi libraries to build this upon seem to be:

  1. Grant (or rather, grant-hapi), has lots of providers (100+)
  2. Bell, is maintaned by hapi

(Also updated my previous post with the provider registration step)

zoepage commented 9 years ago

@janl you mentioned, we already decided which hapi plugin we want to use?

zoepage commented 9 years ago

We'll use Bell.

@christophwitzko and I talked the flow through.

  1. The user in the app wants to signInWith('github'). The button has a bind of the plugin method.
  2. We have checkboxes in the pocket pocket (Admin Dashboard Frontend) with possible services, where you also can add the app_token and secret after you registered your application for a specific service.
  3. We are making a request to the service_auth_server with the user_id and optional other parameters. The most important param here would be the callback URL we'll generate out of app_domain/dynamic_user_id/ so we can get the callback here and just attach the params in the callback from the auth_server. Because of leaving the application, we're losing scope. So we can't just fire a callback / promise. We need to solve this in another way and this is the solution we came up with.
  4. The service_auth_server magic happens here and the params get attached to callback URL, so we get app_domain/dynamic_user_id/params back.
  5. We can set the params like auth_token and secret to hoodie.account.user.set('github') The set is an internal method. We are still thinking about making the get external. Would it make sense or would we write a wrapper maybe? (especially @gr2m?)
  6. fire back a callback and reload on success or show an error including a message for the most common errors (like wrong credentials).

imag4799

We came up with some suggestions for hoodie.account.user JSON scheme, because we have 3 ways to verify the application.

  1. hoodie.account.signIn(usr, pwd) or hoodie.account.signUp(usr, pwd, pwd2) This works as we already know.
  2. hoodie.account.signInWith(service) Here the user is logged in already and can connect a service to the existing account. We would just save the service credentials to the user account (like auth-token and secret).
  3. hoodie.account.signInWith(service) Here it's the very first sign-up from the user including the creation of the account and verification by a service. It would be nice, if we could save more than just the auth-token and the secret here, e.g. the name, the e-mail address, user_id from the service etc.

We thought about also saving things like name or e-mail within the accout.user obj, if the signUp is initial and the name is undefined.

imag4800

espy commented 9 years ago

Maybe we want signUpWith() for the last example, since it's possible that some apps (like ubersicht) would be fine with just oAuth. So signUpWith() would create a new user object and could also take custom user data.

gabrielmancini commented 9 years ago

we can get some ideas from here: https://oauth.io/home

gr2m commented 9 years ago

Because of leaving the application, we're losing scope

Can't we open it in a new window, so that we can inform the host about error / success? Does it need to be a redirect?

gr2m commented 9 years ago

The .set() is an internal method

See the dream api for .get and .set and how to restrict them on a property level over at https://github.com/hoodiehq/hoodie-account-server/tree/api#custom-user-data

Basically, we could define user accounts using JSON Schema, with an additional "access" property, see https://github.com/hoodiehq/hoodie-account-server/tree/api#configuring-the-server

gr2m commented 9 years ago

hoodie.account.signUp(usr, pwd, pwd2)

it's hoodie.account.signUp(usr, pwd). Checking password confirmation is app specific, out of scope for hoodie

gr2m commented 9 years ago

We thought about also saving things like name or e-mail within the accout.user obj, if the signUp is initial and the name is undefined.

Yeah, I wonder if this is app-specific, or if we can pre-configure on what to do with it. We could also store meta data coming from 3rd parties like GitHub, Facebook in special user properties?

If I don't have a an account yet, and I do hoodie.account.signInWith("github", options), a user account needs to be created in our app, so we need to set the username from something, right? I wonder if

How about something like this?

hoodie.account.signInWith("github", {
  // available properties for each provider should be documented somewhere :)
  matchProperties: {
    username: 'email',
    fullname: 'name',
    avatarUrl: 'avatar'
  },
  // other options
})

?

gr2m commented 9 years ago

Here the user is logged in already and can connect a service to the existing account. We would just save the service credentials to the user account (like auth-token and secret).

That is very interesting, I haven't thought about this use case yet, I thought it might be out of scope. I thought we only think about signing the user in, without any background integrations.

But thist makes totally sense, and we should think it trough. I guess we need to somehow expose them to plugins? E.g. I could imagine to have a plugin that checks all user accounts every few hours, and if they are "connected" to dropbox, I'd load their data and back it up to dropbox? Something like that? Maybe we should discuss the back-end / plugin api as well?

zoepage commented 9 years ago

Can't we open it in a new window, so that we can inform the host about error / success? Does it need to be a redirect?

Not found another solution for this yet. Where are the issues you see in the solution we came up with?

it's hoodie.account.signUp(usr, pwd). Checking password confirmation is app specific, out of scope for hoodie

cool!

We thought about also saving things like name or e-mail within the accout.user obj, if the signUp is initial and the name is undefined.

Yeah, I wonder if this is app-specific, or if we can pre-configure on what to do with it.

I think, we could do this for signInWith() when we don't have an account already. Here a second method signUpWith() would make sense, so we would strictly differentiate the behavior.

This would mean: signInWith('github') -> usual signIn behavoir signUpWith('github') -> adding additional information to general user account

hoodie.account.signInWith("github", {
  // available properties for each provider should be documented somewhere :)
  matchProperties: {
    username: 'email',
    fullname: 'name',
    avatarUrl: 'avatar'
  },
  // other options
})

+1 on that

But thist makes totally sense, and we should think it trough. I guess we need to somehow expose them to plugins? E.g. I could imagine to have a plugin that checks all user accounts every few hours, and if they are "connected" to dropbox, I'd load their data and back it up to dropbox? Something like that? Maybe we should discuss the back-end / plugin api as well?

For the schema we thought about:

hoodie.account.signInWith("github", {
  // available properties for each provider should be documented somewhere :)
  service: {
    github : {
      username: 'email',
      fullname: 'name',
      avatarUrl: 'avatar'
    },
    facebook: {
      username: 'email',
      fullname: 'name',
      avatarUrl: 'avatar'
    } ...
  },
})

We should generalize the service data, we want to store.

gr2m commented 9 years ago

I'm still not convinced that we need both, signInWith and signUpWith. From an app user perspective, here's a typical example from buffer.com

screen shot 2015-07-16 at 15 34 24

There is only one button to sign in with Twitter / Facebook / LinkedIn. And that buttons works no matter whether the account exists already or not.

signInWith('github') -> usual signIn behavoir signUpWith('github') -> adding additional information to general user account

I'd only take information from github when the account is created initially, not when it already exists. So again, I think only one .signInWith(provide, options) method will suffice.

espy commented 9 years ago

Ok, I'm re-convinced that signUpWith() might not make any sense, sorry for introducing confusion :D We'll still be able to pass in user data with signInWith(), so that should be enough.

Adding custom data to an oAuth user would have to be a two-step process anyway, first auth, then add custom data, and at that point the app logic can decide whether to even show the inputs for the custom data based on whether it exists in the user doc already. So that should work.

zoepage commented 9 years ago

... the inputs for the custom data based on whether it exists in the user doc already. So that should work.

I don't think, we should have any inputs for the custom service data. I think a getter would be enough for that.

espy commented 9 years ago

Absolutely, inputs would be app logic too, was just an example :)

zoepage commented 9 years ago

Cool :)