caddyserver / caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
https://caddyserver.com
Apache License 2.0
57.03k stars 3.99k forks source link

Idea: add 3rd party login middleware? #246

Closed txchen closed 8 years ago

txchen commented 9 years ago

Caddy is cool and I am starting to use it to replace nginx. Thanks for making this project!

Right now I am using https://github.com/bitly/oauth2_proxy to support login as google account. IMO, this is better than basic auth.

How about adding this as a middleware in caddy? Running one process is better than two.

Just want to confirm that you guys like the idea or not. If so, I can implement and send PR.

@mholt

mholt commented 9 years ago

That would be excellent - is your idea related to #109?

txchen commented 9 years ago

Oh, yes, that's similar to #109 @Karthic-Hackintosh How is your progress so far?

BTW, I think we can use jwt so that session is not needed.

hackintoshrao commented 9 years ago

@txchen : The progress in developing this add on has been painfully slow , I would love to collaborate or help wherever necessary if you could kickstart this

txchen commented 9 years ago

@Karthic-Hackintosh I can have a try to see if it will be smooth or not. My current thinking is to exchange the code for access_token, then issue jwt and drop it in cookie. So that we don't need to use a session store. Let me try to implement google login first.

coolaj86 commented 8 years ago

I would be interested in working on an oauth3 proxy, which would support google, facebook, and openid connect, and any number of federated / decentralized domains.

I'm in the middle of rewriting some core parts of my oauth3 server that are preventing it from utilizing multiple cores, but I'll touch base with you once I'm able to focus again on the client code.

mholt commented 8 years ago

@coolaj86 @txchen I just found this recently, it looks awesome and I bet it could be helpful: https://github.com/dghubble/gologin (no oauth3 though)

coolaj86 commented 8 years ago

That's probably what I would need to start with to build a go oauth3 client. Essentially the oauth3 client is just an oauth2 client turned inside out where one object has the config is fed in on each method use rather than 30+ objects where the config is hard coded - functional instead of object oriented.

stuart-warren commented 8 years ago

CoreOS have an OpenID Connect library https://github.com/coreos/go-oidc

coolaj86 commented 8 years ago

Here's the million dollar question:

How should this work? As in what's the use case?

Are we trying to say "only people with a facebook account can log in"? Because that's not really doing much. Also, facebook user app-scoped ids, so to specify a list of individuals you need to know their ID with your app (which should be not hard to find in the developer console, I think). I guess you can get email permission and then you could know that ahead of time.

I've written a process in both ruby and node (and would easily port to go) that works perfectly for any oauth2 provider because it just uses an iframe to push everything except for the secret back to the browser, and that gives you access to the person's social token, but it doesn't protect any particular individual.

Anyway, once I understand better what it is that we want to do, I'd like to look more into it.

captncraig commented 8 years ago

Here's my idea:

You configure the server with your clientId, ClientSecret, desired scopes, and paths to lock down, etc...:

oauth github {
    clientId ...
    clientSecret ...
    scopes user,repo
    path /foo #will require auth
    path /bar
    login_url /oauth/login # route to initiate oauth flow
    callback_url /oauth/callback
    cookie_secret aslkfjaslk;fjals;kfjlask;fj
    redirect_when_blocked /login #your page to show if they get redirected from a protected page
}

The middleware on all requests will decrypt the session cookie and pass on headers to the downstream servers (I am assuming this will mostly be used with the proxy module). Will probably pass X-OAUTH-TOKEN as well as any provider specific information that may help (for github we may query the api for userId, avatar, username etc.. and pass those on as additional headers for example.)

If a route is "protected" and you don't have a valid cookie, it will redirect you to your landing page, and you can initiate the flow from there.

The idea would be that additional providers can be added over time, as each may require a bit of tuning to get the semantics just right. Oauth is pretty hard because of the minor differences in implementations.

coolaj86 commented 8 years ago

So I'm cruzin' around example.com/public

and then I see this freakin' cool link to example.com/private

and I click on it and I'm like "oh no: it needs authentication!"

a github account? What? I have no idea how I would get one of those. I guess I just won't be able to visit example.com/private

ah shucks!

Except that anyone can get a github account... so I'm missing an important piece of how this would be useful. How would one limit access? What is the advantage to putting a login in front of static content that absolutely anyone can access? How is this different from putting up a div with a no-show class that is shown when you successfully click a "login with facebook" button?

Technically this is totally doable, but I'm not understanding the use case.

Tell me a story so I can understand the use case.

As far as adding providers, OAuth3 will work with any existing OAuth2 provider and OIDC will work with anyone who has enabled OIDC in their OAuth2 service.

captncraig commented 8 years ago

I have a web app that accesses your Github account to show you your repos or do magic stuff. I don't want to code up the oauth api flow because I'm lazy. I can make requests for you if I have a token.

So you can go to /public and see my marketing materials, but if you try to go to /private you will be redirected to a login page. Once you log in, you can access private, and my app can make requests and stuff. It doesn't make a ton of sense around static content unless you restrict it to a particular org or team (which is not a bad idea).

The middleware can abstract away a lot of the login flow stuff so my app can just get a token in a header and start using it, and trust that someone upstream from him has handled authentication.

I mostly want this for sites I run behind caddy's proxy

coolaj86 commented 8 years ago

Cool. Next question:

Why not just use the Implicit Grant? Authorization Code tokens can only be used server-to-server.

Having the server exchange Authorization Codes seems to add complexity where the browser could just use Implicit Grant tokens.

captncraig commented 8 years ago

My intent is to keep this entirely server side. I don't have much understanding of implicit grant and how it works.

coolaj86 commented 8 years ago

When you say "make requests" I assume you mean that your web app in the browser will make requests with a github token, correct?

What if you just had to include something like this

<script src="./scripts/oauth3.js"></script>
<script src="./scripts/login.js"></script>

login.js:

Oauth3.login({
  provider: "github.com"
, clientId: "xyz"
}).then(function (token) {
  $.ajax({ headers: "Authorization": "Token " + token, url: "https://github.com/users/joe/foo/stars" }).then(function (results) {
    console.log(results);
  });
});

Would that meet your needs?

captncraig commented 8 years ago

No. I don't ever intend to have a browser make requests to github. In my particular use case I will be issuing api requests with go-github, but it could potentially be an app in any language interacting with the oauth provider.

coolaj86 commented 8 years ago

Okay, so this isn't a web app but rather a form-based server processor.

I'll think on that.

And you'd be using a proxy directive like proxy /protected http://localhost:4000

stephanbuys commented 8 years ago

Would it be possible to integrate JWT on the server level to generically protect any proxied service? The JWT PSK might be particularly well suited?

jnflint commented 8 years ago

I'd second @stephanbuys for a JWT solution. I'd evisage this as a simple authorisation middleware. A full blown login system would be rather more complicated especially if it needed to cater for a variety of use cases. A authorisation system could be built around validating JWT and integrating to an external system that does the authentication and identity management. Something along the lines of https://github.com/auth0/nginx-jwt which provides authorisation for nginx acting as a reverse proxy. With JWT the middleware would be sessionless. Start basic and then extend it to handle validating additional claims and processing refresh tokens as well.

stephanbuys commented 8 years ago

@jnflint agreed, the idea is authorisation, not authentication, that can be done anywhere else. It would be useful as a reverse proxy as well as a way to protect resources at certain paths (or globally).

BTBurke commented 8 years ago

I have a couple APIs that are written in Go that share a JWT middleware implementation (written for the Gin framework). I'm thinking about moving it into a Caddy middleware so that I can put relatively dumb resources behind it and have Caddy handle authorization while authentication happens elsewhere as @stephanbuys mentions above.

The gist of how it would work is as follows:

  1. JWT secret stored in an environment variable (similar to how auth0/nginx-jwt handles it). Or, possibly written right in the Caddyfile.
  2. Middleware validates the token, returns 401 if not valid
  3. Sets headers for the commonly-used fields: subject, expiration, audience, issuer, etc. Something like X-Token-Claim-Subject: some user. Any downstream resource can act on the basis of these headers without messing around with the token.

Am I missing any other desirable features?

jnflint commented 8 years ago

@BTBurke You might consider for 2. an option of being able to configure a redirect instead of the 401. This would allow via configuration to redirect to a login page of an authentication server. Have the 401 as a option as well.

As well as setting some custom headers, I'd still pass the jwt authentication header through for any apps that want to deal with that directly. A header that might be useful to set is PHP_AUTH_USER I believe this is used by a number of common PHP apps.

My use case is I have a couple of legacy PHP apps. They have their own authentication and user database. The apps still work ok so we will not replace them (yet) Some new shiny apps we are building will use a JWT based authorisation. It would be great to have a common identity and authentication system so we don't have to play around much integrating legacy code. So solution is to put the new and legacy behind a proxy that has a common authorisation method that can use a common authentication system.

mholt commented 8 years ago

For what it's worth, I also found https://github.com/markbates/goth which facilitates authentication.

mholt commented 8 years ago

As of today, @captncraig is working on an OAuth add-on. And it looks like it'll be amazing. :+1: I do not believe JWT is part of it, however, so perhaps that should be part of a separate add-on? (I prefer small and simple middlewares.)

BTBurke commented 8 years ago

I'm done with the JWT validation middleware. The tests look good. I haven't head time yet to add it to the list of middleware handlers or work up the docs but will do that very soon. If you have a minute, would appreciate a look at it.

https://github.com/BTBurke/caddy-jwt

mholt commented 8 years ago

@BTBurke Wow! :+1: Looks fantastic!

Is it normal for the JWT headers to start with X-? If I recall correctly, the prefix for "non-standard" headers was deprecated. (I'm not familiar with JWT so if that's part of a spec, then no worries.)

Your return values in ServeHTTP look good. Really glad to see tests too.

BTBurke commented 8 years ago

Passing the claims as headers isn't really a normal thing to do. If they are going to parse the token to get the claims using a library in their downstream application then they don't really need Caddy to handle the JWT auth step since they might as well validate the token while they are at it.

I figured passing claims as headers would be the most user friendly way for someone to act on the claims without resorting to a JWT library. I didn't know they got rid of the old "X-" headers. I can change them to "Token-Claim-Foo" or something.

mholt commented 8 years ago

Sure! Using headers is fine, I only recommend dropping the X- prefix. :smile:

BTBurke commented 8 years ago

Easy enough. I'll work on the docs this weekend.

jnflint commented 8 years ago

Looks good, thanks for the contribution. I note that you will pass on the JWT with r.Header.Set("X-Token", vToken.Raw) Just wondering if this is necessary? I am new to Caddy and not yet using it but does it strip the existing headers (or cookies) before passing to downstream? I would have assumed the authorisation header would pass through for any applications that can deal with that themselves so that would be redundant.

BTBurke commented 8 years ago

yeah that's true, didn't think about it that way. My thought was that since there are three ways to pass the token in the first place, it would be good to have it in one place you can always look. But, you also probably know how you passed the token originally so maybe it's redundant.

BTBurke commented 8 years ago

Also planning on adding one enhancement before the pull request. The biggest win would be to let authorization be dependent on the value of a claim. For example, securing a path based on the value of the username or role.

BTBurke/caddy-jwt#1

mholt commented 8 years ago

@BTBurke Very cool -- I see your pull request to add it to the build server. We still need docs for the site, but I see your readme is pretty thorough. Are you working on docs already, or would you like me to help with them, just taking what your readme says?

captncraig commented 8 years ago

I'm curious how you actually generate jwt tokens. This can handle them, and it looks pretty straightforward, but how do you actually log in and create the token?

BTBurke commented 8 years ago

@mholt I just submitted a pull request for the docs. It's basically the same as what's on the readme.

@captncraig this only handles the authorization piece. You'd still need something to handle authentication and issuing the token elsewhere. Since that would normally be interacting with a database of users, it's probably beyond the scope of this middleware.

You could create some other kind of login middleware that stores the user info in a flat file and issues a token for very simple sites that don't need a full blown DB.

For my use case, this works pretty well. I have a separate service that handles logins. It issues a long-lived token which is used to talk to an API that is separate from the login service.

Xe commented 8 years ago

I am very interested in this. Please merge.

mholt commented 8 years ago

@Xe I've merged @BTBurke's JWT middleware, just waiting to hear back about one small question before I deploy.

mholt commented 8 years ago

@Xe The jwt middleware is now live. https://caddyserver.com/docs/jwt

mholt commented 8 years ago

@captncraig I believe you have made some good progress on this. What's left on the roadmap? (I know we talked about it on chat, but, that's hard to find now.) Does this issue need to stay open?

dcod3d commented 8 years ago

saw that this was just closed.. is it now implemented?

mholt commented 8 years ago

Nah, but we don't really track development of add-ons (now "plugins") in issues in this repo anymore. There's nothing for Caddy maintainers to act on here. Also I haven't heard much progress on it since about May.

smancke commented 7 years ago

For those, still interested in something like this, take a look at:

https://github.com/tarent/loginsrv/tree/master/caddy

There I started a plugin for the login part, which works fine together with @BTBurke's JWT plugin. It is very fresh and currently only two simple login backends are provided, but I plan to support more, including oauth.

axelson commented 10 months ago

I found this thread via a Google search and I then implemented a system using forward_auth which was added to Caddy after this thread: https://caddyserver.com/docs/caddyfile/directives/forward_auth