Skip to content

Conversation

@janl
Copy link
Member

@janl janl commented Nov 8, 2015

First proposal is a new endpoint /_login_as/, but could be any
other endpoint, even included in /_session, this is just to demonstrate
the feature.

On success, the endpoint returns a JSON object that looks like this:

{"auth_token": "amFuOjU2M0U0MDREOsGlD2HF6fday16PZGb0vIMDkWCw"}

It returns 404 for nonexistent user or when in admin party mode.

One use-case for this is building delegated authentication mechanisms
in middleware on top of CouchDB (like Oauth), while still being able
to log into CouchDB via it's native auth mechanisms without having
to proxy all requests and their variations, for e.g. replication.

This implementation is for 1.x.x for now (don't judge me), I'll
also supply a 2.x version.

First proposal is a new endpoint /_login_as/<username>, but could be any
other endpoint, even included in /_session, this is just to demonstrate
the feature.

On success, the endpoint returns a JSON object that looks like this:

  {"auth_token": "amFuOjU2M0U0MDREOsGlD2HF6fday16PZGb0vIMDkWCw"}

It returns 404 for nonexistent user or when in admin party mode.

One use-case for this is building delegated authentication mechanisms
in middleware on top of CouchDB (like Oauth), while still being able
to log into CouchDB via it's native auth mechanisms without having
to proxy all requests and their variations, for e.g. replication.

This implementation is for 1.x.x for now (don't judge me), I'll
also supply a 2.x version.
@kxepal
Copy link
Member

kxepal commented Nov 8, 2015

Basically that's how Proxy Auth works and should be used for.

@kxepal
Copy link
Member

kxepal commented Nov 8, 2015

To make Proxy Auth works only for admins, just require secret for requests and don't share it with anybody else.

@janl
Copy link
Member Author

janl commented Nov 8, 2015

@kxepal not quite, I do want to hand out auth tokens for existing CouchDB users, not populate a userCtx from an external source.

@kxepal
Copy link
Member

kxepal commented Nov 8, 2015

@janl ok, than I have to ask what problem you're trying to solve. What is the use case. Because I don't see any difference between Proxy Auth.

In both cases we have some middle ware. In both cases it have super privileges to run auth for any user. In both cases this used to run some requests in the name of some other user. And in both cases you know the user name you want for delegate the auth. The difference is in with what object you runs this delegated auth: with special headers or with a cookie.

@rnewson
Copy link
Member

rnewson commented Nov 8, 2015

@rnewson
Copy link
Member

rnewson commented Nov 8, 2015

Whichever we use its time for sha-2

@rnewson
Copy link
Member

rnewson commented Nov 8, 2015

@kxepal the difference is you don't need to put couch behind a proxy

@kxepal
Copy link
Member

kxepal commented Nov 8, 2015

@rnewson Can you elaborate this? Especially, how would you make this works without admin password leak?

@kxepal
Copy link
Member

kxepal commented Nov 8, 2015

@janl What did I understand from this:

  1. There are User, App and CouchDB
  2. User runs auth on App as Jan
  3. App requests CouchDB POST /_login_as/jan
  4. CouchDB meanwhile must have this user created
  5. CouchDB returns App auth_token
  6. App returns user auth_token
  7. User sets this auth_token as cookie and starts to communicate directly with CouchDB

Is that correct?

Questions:

  1. For this auth schema it seems I have to mirror App users into CouchDB in order to make them auth and to revoke their token when I remove them. Is that correct?
  2. If its is why User cannot auth directly on CouchDB?
  3. If this auth schema doesn't assumes direct User auth with CouchDB and auth_token expires, what is the workflow for it refresh?

I think some use case would be nice to see.

@rnewson
Copy link
Member

rnewson commented Nov 9, 2015

@kxepal in the delegated_auth code, an admin calls _delegated_auth and gets a response body that includes a session cookie. the admin then gives that session cookie to someone else to use. In this case, typically, the 'admin' is a machine.

@KlausTrainer
Copy link
Contributor

I'm all +1 for making authentication and authorization more flexible
(and thereby more powerful), given that we don't compromise on security.
This is a nice and simple feature, and I don't see a reason to not
support it.

The feature is complementary to proxy authentication in the following
ways:

  1. It can be used without a proxy.

  2. A corresponding user document is required for successful
    authentication.

  3. There is a possibility to invalidate all of a particular user's
    authentication cookies. (This can be achieved e.g. by setting the
    password field when updating the user document. This works even if
    the password doesn't get changed at all.)

I also have a use case for the feature.

Together with @robertkowalski, I've been working on a small plugin that
provides passwordless authentication by sending the authenticating user
a sign-in link via email [1]. The sign-in link contains an
authentication token, which gets exchanged for a CouchDB authentication
cookie the first time a user follows the sign-in link. Thereby, we want
to make sure that that every authentication token can only be used once,
and that for a given user there can only be one valid authentication
token at a time.

Previously, we would always set a new password for each sign-in, and
thereby involuntarily invalidate all authentication cookies for that
user when creating a new authentication token. This is big show-stopper
given the fact that people nowadays tend to switch between different
devices all the time. Not being able to authenticate with more than one
browser at a time is not acceptable. Aside from that, we however still
want to make it possible to invalidate all of a particular user's
authentication cookies.

Although we found a solution that matches our requirements, it took us
quite some time. Implementing the same functionality would have been way
easier if this feature had been available. The resulting code would be
less complex and therefore easier to understand. I guess I don't need to
explain why this is critical when dealing with sensitive stuff like
authentication.

[1] https://github.com/KlausTrainer/couch_email_auth

@kxepal
Copy link
Member

kxepal commented Nov 10, 2015

I'd played around with both @janl and Cloudant solutions and figure out the cases when this feature becomes very useful. Sorry, I have to take back my scepticism (:

However, important difference between these two implementations that Jan require to delegate auth for real and existed CouchDB user while Cloudant version acts more as Proxy Auth - you may delegate auth to any user, even non-existed ones.

With given default 30 days timeout this makes things like token revoke very hard to make. I only found the one way is to change the salt what is basically means that I revoke all the tokens. With Jan's approach I can do this per user basis. In the same time it requires my app to register in CouchDB every user for which I will delegated auth with all the required roles. A little bit more work to do, but it brings more order in the house.

Jan, please continue your work here.

However, it's still not clear how users with delegated auth will handle expired token case (when we cannot prolong it automagically). CouchDB will return 401 please auth, but to auth I have to ask some third party service. Probably couch_httpd_auth with proper authentication_redirect will help here, but it's a feature that is hard to make it work properly /: Thoughts?

@kxepal
Copy link
Member

kxepal commented Nov 10, 2015

@KlausTrainer Nice use case! Thanks for sharing (:

@KlausTrainer
Copy link
Contributor

@kxepal You're welcome :)

Regarding token revocation with delegated authentication: You probably don't want to care about revoking tokens for individual users. The whole point of delegated authentication is to not care about such issues, I guess ;) You either trust the identity provider completely, or you don't trust it at all. That is, you do want to be able to revoke all tokens that have been issued by the identity provider, without revoking all other tokens (cookies) at the same time.

Btw., regarding delegated authentication, I'd like CouchDB to support JSON Web Tokens (JWT). I created a working prototype for JWT support a while ago, see https://github.com/KlausTrainer/couchdb-couch/tree/jwt. The larger parts that are still missing are tests and documentation. I definitely want to get back to that, but feel like working on 2.0 blockers is a bit more important right now ;)

@kxepal
Copy link
Member

kxepal commented Nov 10, 2015

@KlausTrainer Well, if I gave someone auth_token for my CouchDB and this someone started to do bad things there, I would like to make sure I can ban him. Yes, I can do that on my third party app side which runs delegated auth, but until he hold auth_token cookie and it's not expired he is still an active user for CouchDB.

JWT support indeed would be nice. Not only as delegated auth, but as alternative to cookie one (because for some cases auth cookies are hard). But indeed, we need to finish blockers and broken compatibility issues first. However, if we can do both - that would be awesome (:

@janl
Copy link
Member Author

janl commented Nov 25, 2015

Glad to see this is useful for people.

@kxepal what should I be working on? My next step would be porting it to 2.x, or is there anything else you’d like to see addressed?

@janl
Copy link
Member Author

janl commented Nov 25, 2015

However, it's still not clear how users with delegated auth will handle expired token case (when we cannot prolong it automagically). CouchDB will return 401 please auth, but to auth I have to ask some third party service. Probably couch_httpd_auth with proper authentication_redirect will help here, but it's a feature that is hard to make it work properly /: Thoughts?

I like the redirect bit. Would be a config option, like we do with _session: https://github.com/apache/couchdb/blob/master/rel/overlay/etc/default.ini#L83

Good point, too!

@kxepal
Copy link
Member

kxepal commented Nov 25, 2015

@janl

  1. CSRF protection by requiring application/json Content-Type in request
  2. Tests are looks too much commented (:
  3. Admin Party TODO

Also, I thought about differences between your and Cloudant version...I think it's possible to have both behaviours:

  • When you require all users to be created in CouchDB with proper roles (yours version)
  • When you can use generic users with custom roles ala Proxy Auth (Cloudant version)

I think both could be useful depending on the use case and that behaviour could be controlled by some config option. How do you feel with it?

@janl
Copy link
Member Author

janl commented Nov 25, 2015

as for revocation as it pertains to this PR: it is no different than the cookie auth we use today. I believe the default timeout for that is 10 minutes: https://github.com/apache/couchdb/blob/master/rel/overlay/etc/default.ini#L85

@kxepal
Copy link
Member

kxepal commented Nov 25, 2015

as for revocation as it pertains to this PR: it is no different than the cookie auth we use today.

Yea, and persistent cookies may bite you here. However, that's indeed not related to this PR, but about various possible cookie auth configuration. Users just need to be careful with.

@janl
Copy link
Member Author

janl commented Nov 25, 2015

I think both could be useful depending on the use case and that behaviour could be controlled by some config option. How do you feel with it?

I’d prefer to do this one feature at a time, if possible. Definitely outside of this PR.

@kxepal
Copy link
Member

kxepal commented Nov 25, 2015

Ok, fine.

@janl
Copy link
Member Author

janl commented Nov 25, 2015

  1. CSRF protection by requiring application/json Content-Type in request

janl@b4bb645

  1. Tests are looks too much commented (:

janl@b00b8f2

  1. Admin Party TODO

janl@0b9af68

@janl
Copy link
Member Author

janl commented Nov 25, 2015

Anything else? :)

@kxepal
Copy link
Member

kxepal commented Nov 25, 2015

Yes: you need to add test/etap/240-login-as.t to Makefile.am or it will be excluded for distcheck and these tests will never run.

@janl
Copy link
Member Author

janl commented Nov 25, 2015

Oh, the redirect bit, yes, I’ll do that next.

Yes: you need to add test/etap/240-login-as.t to Makefile.am or it will be excluded for distcheck and these tests will never run.

Of course, thanks :D

@janl
Copy link
Member Author

janl commented Nov 25, 2015

hm no, wait, where do I have to add this in Makefile.am? All we call there is $(top_builddir)/test/etap/run $(top_srcdir)/test/etap

@janl
Copy link
Member Author

janl commented Nov 25, 2015

oh in etap/Makefile.am

@kxepal
Copy link
Member

kxepal commented Nov 25, 2015

Yes, because this call will happens after tarball generation and autoconf won't include that test file there because it operates with the whitelist.

@janl
Copy link
Member Author

janl commented Nov 25, 2015

Done

@kxepal
Copy link
Member

kxepal commented Nov 25, 2015

Cool! Let's squash all together and I think it's done (: LGFM, thanks!
@rnewson +1?

@janl
Copy link
Member Author

janl commented Nov 25, 2015

I’ll have to ponder the redirect thing for a little longer. It would definitely work to keep this in the client, sure more work there, but they could have a logic of “if CouchDB API calls come back 401, talk to the auth server first for a new token”. Then we don’t have to know.

@janl
Copy link
Member Author

janl commented Nov 25, 2015

Happy to land this as is, and decide upon the redirect helper later :)

@kxepal
Copy link
Member

kxepal commented Nov 25, 2015

Indeed. Just an idea: pass URL for auth page with POST /_login_as and include it into cookie data. When it expires, extract that URL from cookie and redirect to there.

@janl
Copy link
Member Author

janl commented Nov 25, 2015

nice one! :)

@KlausTrainer
Copy link
Contributor

+1 to all of this :)

@kxepal
Copy link
Member

kxepal commented Nov 26, 2015

[off] @KlausTrainer , may be you also backport JWT auth to 1.7 for experiment? (;

@janl
Copy link
Member Author

janl commented Nov 28, 2015

Linked 2.x versions above.

@kxepal Can you help me port the 1.x tests to the new testing system? I experience very little experience with that.

@kxepal
Copy link
Member

kxepal commented Nov 29, 2015

@janl Sure! Will handle it over today.

@janl
Copy link
Member Author

janl commented Dec 9, 2015

@kxepal thank you! :)

@kxepal
Copy link
Member

kxepal commented Dec 9, 2015

Ohai. Sorry, I eventually didn't expect such lack of free time week ago. Basically, what I did is turn your PR into pumpkin by dropping etap. Nothing personal, just improvements (:

That only means, that we'll have the one test suite for this feature for both versions. Hold on and await PR for your fork to rebase. Thanks!

@janl
Copy link
Member Author

janl commented Dec 10, 2015

@kxepal nothing to wait for as far as I am concerned.

@wohali
Copy link
Member

wohali commented Mar 19, 2017

@janl @kxepal This PR has merge conflicts now. It'd sure be nice to see this hit along with the 2.0 version. Are we still motivated to do so?

@wohali wohali added this to the 1.7.0 milestone Apr 30, 2017
@alxndrsn
Copy link
Contributor

@wohali @janl I may be interested in reviving this for couchdb 2.x, depending on our roadmap. Are there clear reasons why it stalled previously?

@wohali
Copy link
Member

wohali commented Mar 5, 2018

I'm going to close this out, simply because new feature development for 1.x has ended.

I'd still very much like to see #373 and associated PRs get merged into 2.x, and/or Cloudant's delegated_auth repo instead.

@wohali wohali closed this Mar 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants