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

Develop user authentication strategy #5

Closed
tobyzerner opened this issue Dec 20, 2014 · 32 comments
Closed

Develop user authentication strategy #5

tobyzerner opened this issue Dec 20, 2014 · 32 comments

Comments

@tobyzerner
Copy link
Contributor

Users will need to be able to authenticate (log in) through the API. It needs to be reasonably secure over both HTTP and HTTPS, just like a standard PHP form login.

I'm unsure of the best way to approach this — presumably just have an endpoint that takes a username/password and gives back a token?

Discuss. Does anyone want to lead this?

@AlexanderOMara
Copy link

Maybe related:

It would be helpful for large-scale usage if the user account system can be overridden by extensions so that it can be integrated with existing account systems. For example, a tech company may want to add support forums for their product, but doesn't want users to have to make a separate account in addition to the main site. I believe WordPress does something like this.

@tobyzerner
Copy link
Contributor Author

@AlexanderOMara Yep, this is definitely something we'll want to look at in the future. I've added it to the Features page. For now, though, this issue is about a basic user authentication strategy.

@cwramsey
Copy link

I've done a good bit of research on this for an API I develop and this is almost exactly the solution we came up with. Pass your username and password via HTTPS one time and get back a token that you can store for later usage. It's a pretty solid way of doing things and as long as it's only passable by HTTPS, you're secure.

@tobyzerner
Copy link
Contributor Author

@cwramsey The problem with this is that it requires HTTPS — and since one of Flarum's goals is ease-of-installation, ideally we don't want to require HTTPS to be set up. Is sending a username/password to an API and subsequently authenticating requests with a token without HTTPS any less secure than the corresponding workflow in a normal PHP app (username/password in login form, subsequently authenticating requests with a session cookie)?

@UTAlan
Copy link

UTAlan commented Dec 22, 2014

How about something like this? http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/

Essentially, it uses a public key and a hash (usually a private key and other data) on the client side, then generates the same hash on the server side and compares the two.

@MartinMalinda
Copy link

JWT could be implemented http://jwt.io/

@tobyzerner
Copy link
Contributor Author

Thanks for the links. Looks like JWT is a nice simple way to go.

@tobyzerner
Copy link
Contributor Author

As I understand it, the JWT approach without HTTPS would still be susceptible to session hijacking in the same way that a standard PHP session-cookie authentication flow is?

And @UTAlan's link (i.e. 2-legged OAuth) would solve this, by authenticating every request using an encrypted private key only known to the client and server.

However, I'm pretty keen to keep things simple (at least to start with) and forgo the complexity of OAuth, instead just encouraging the use of HTTPS. What do you all think?

@thomje112
Copy link

I'm not wanting to buy a SSL certificate just to be able to use Flarum, however.

@tobyzerner
Copy link
Contributor Author

@tymondesigns
Copy link

I have built a laravel jwt auth package that may be of some use - https://github.com/tymondesigns/jwt-auth

I am currently working on big release on the develop branch, which will be much more flexible and extensible.

@solhuebner
Copy link

Not using https will get you lower positions on search engines like Google. And as certificates will be free soon and are cheap you should give your community (forum) some kind of security :-) I vote for https

@qrokodial
Copy link

relying on every host being able to use HTTPS should be avoided in my opinion. think about shared hosts with no SSH access and server hosters who are slow to implement Let's Encript's free HTTPS (or decide not to do it altogether)

I mean let's be real here. server hosts are quite slow to adapt to new changes - how many hosts are still running ancient versions of PHP for example?

@tobyzerner
Copy link
Contributor Author

@qrokodial Right, well said. Definitely in the mid-to-long-term we will want to implement something like OAuth. But in the short-term, just to kickstart development, I'm going to set up something really simple.

@tobyzerner
Copy link
Contributor Author

OK, doing some more reading/thinking:

  • @UTAlan Re: the two-legged oauth stuff — I don't see how this is going to help security in any way, purely because the client must know the private key — and because the client is a JavaScript application, anyone can just look at its source in the browser and grab the private key, and then use that to send fake requests. Is that correct or am I missing something?
  • Reading this StackOverflow question, it just looks like there's no way to securely do auth stuff without HTTPS — any more so than in a normal PHP app like esoTalk running without HTTPS. Regardless of what fancy encrypting or tokenizing we do, the username/password still gets sent in plain text when logging in, so it's all for nothing.

So: I'm going to implement JWT using @tymondesigns package (excited about 0.4!) and this will at least be a good way to validate claims. Flarum will run insecurely without HTTPS just like esoTalk or any other PHP forum software, and the recommendation will be to set up HTTPS, use something like CloudFlare Universal SSL, and/or offer SSO with external providers (Facebook, etc.) for which at least username/password isn't sent in plain text.

Does that sound reasonable? Any thoughts?

@tymondesigns
Copy link

@tobscure Firstly, thanks for taking the time to check out my package 😄

I hope to release 0.4 hopefully within the next couple of weeks, and I am currently writing up the wiki.

Also note that 0.4 will be for Laravel 4, and I plan to release 0.5 for Laravel 5 shortly after.

Just out of interest, do you plan to keep flarum on Laravel 4 long term, or can you see an upgrade path at some point?

Thanks!

@tobyzerner
Copy link
Contributor Author

Plan to upgrade as soon as L5 is released. See #2

@Allineer
Copy link
Contributor

Flarum will run insecurely without HTTPS just like esoTalk or any other PHP forum software, and the recommendation will be to set up HTTPS

I think that's will be enough for the core. All kinds, more secure solutions can be implemented with extensions in the future.

@tobyzerner
Copy link
Contributor Author

@tymondesigns Quick question, there's no way to manually invalidate JWTs is there? i.e. if someone sniffs and steals the token, there's no way to invalidate their claim. Now I'm thinking it might be better to just use Laravel's built-in remember_token system, something like this:

  1. Send username+password to /api/auth/login. Use Laravel's Auth to attempt login, set remember_token on user's database record if it doesn't exist, and respond with the remember_token.
  2. In subsequent requests, send Authorization: [remember_token] header. When booting Flarum, check for its presence and log the user in using that.
  3. Calling /api/auth/logout will refresh the remember_token, thus invalidating the token if it was stolen.

@tobyzerner
Copy link
Contributor Author

@tymondesigns
Copy link

@tobscure on the current 0.3 release there is no feasible way to invalidate tokens individually.
However the 0.4 release introduces the idea of a blacklist. Which allows tokens to be invalidated, (and indeed refreshed).

The token TTL can be kept very short in this case, and refreshed transparently; until, for example, the user becomes inactive (or a longer period has passed) and they must re-login again.

I expect you've done a fair bit of reading on the subject already, but here are some links that highlight some issues with using cookies and jwt's. (CORS and session scalability are big ones for me)

https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/
http://www.slideshare.net/derekperkins/authentication-cookies-vs-jwts-and-why-youre-doing-it-wrong

Also I assume both appraoaches are vulnerable when not in a ssl environment?

@tobyzerner
Copy link
Contributor Author

@tymondesigns

Also I assume both appraoaches are vulnerable when not in a ssl environment?

Yes, I think this is OK though (no less secure than a standard PHP app in a non-SSL environment). We will strongly recommend users to set up HTTPS or use something like CloudFlare Universal SSL.

I expect you've done a fair bit of reading on the subject already, but here are some links that highlight some issues with using cookies and jwt's. (CORS and session scalability are big ones for me)

Thanks for the links! I should clarify: I'm not comparing cookies vs. JWTs, but rather standard tokens vs. JWTs.

With normal tokens the API is still completely stateless and will work with CORS — it just stores a randomly-generated token in the database for each user and uses that to authenticate each request (a whitelist approach) rather than containing the user's identity within the token and then having to store blacklisted tokens. I've implemented this in Flarum's API (ad269fd).

@designgrill
Copy link

Sorry to have discovered this thread a bit later. Here are my 2 cents.

JWT will give portability (session info can be stored in token itself) but not security as such. Any traffic over HTTP can be sniffed, whether its username+password, cookies, oauth tokens, or JWT. Once sniffed, backend won't be able to make any distinction and will gladly accept the stolen token/credentials.

Keeping it simple might be the way out. If a forum owner is concerned about security, he better get HTTPS. With SNI and all, its not that difficult and costly anymore. Thats what move from OAuth1 to OAuth2 essentially was. It made things much simpler.

OAuth2 is not complex and is a very good candidate for being the default authentication.

@tobyzerner
Copy link
Contributor Author

@designgrill thanks for the thoughts. I've come to realise that plain ol' tokens aren't flexible/powerful enough (e.g. can't set an expiry date so "remember me" functionality is implied, which would be bad if using Flarum on a public computer.) Very likely I will implement OAuth2 soon.

@designgrill
Copy link

'remember me' can be done regardless of the token type. For conventional session IDs, you store the expiry in the session storage. For JWT that can be stored in the token itself. And in both cases the cookie can have corresponding life time.

Looking forward to your OAuth2 implementation. That might also enforce an API first design which will go a long way!

@tobyzerner
Copy link
Contributor Author

OK, I've been playing around with OAuth2 and I've come to the conclusion that it's overkill for Flarum.

  • The whole concept of "clients" is useless to us. Flarum's Ember app can't protect a client secret, so we would have to use the Resource Owner Password Credentials Grant Type without client credentials — and thus any third-party app could do the same. Also, we want any third-party apps to be compatible with all Flarum forums without any setup, and setting up a client_id and client_secret for each app on Flarum instance simply isn't viable.
  • Without clients, the concept of "scope" is also useless. Since Flarum's Ember app needs full access, but it can't be differentiated from any other client (because it can't protect a secret), then full access is implicitly available to all clients.
  • Since in our case the resource server and the authorization are the same entity, refresh tokens are useless.

Ultimately, it seems to me that we're left with a simple endpoint which accepts a username-password combo and spits out an access_token which doesn't expire. That's not really OAuth2.

So I'm thinking of a really dumbed-down, simple solution instead:

  1. We have an API endpoint at /api/token. It checks a username-password combo and then generates and stores a limitless token, associated with a user ID, in an access_tokens table.
  2. Any requests to the API can include an Authorization: Bearer [access_token] header to be authenticated.
  3. Flarum's Laravel app needs to support session/cookie-based authentication, independent of the API (this is to do with SEO #13). So, Flarum's Ember app authenticates with a special non-API endpoint (e.g. /login), rather than the API standard /api/token. This endpoint authenticates via session (and cookie if "remember me" option is true) using Laravel's standard Auth setup. It then generates an access_token for that user and injects it into a JavaScript payload on the page which the Ember app can use to consume the API.
  4. If the user returns to Flarum after a while, then the Laravel app will match against their remember cookie and log them in as in (3).
  5. If the user logs out, the session/cookie will be destroyed, as well as all access_tokens associated with that user.

How does that sound?

@Allineer
Copy link
Contributor

Simple. Extensible. Awesome :)

@stratusrob
Copy link

This should be flexible and extensible for possible integration on any third party apps in the future.

@designgrill
Copy link

You are right about OAuth implementation in this case be without any client credentials and Resource Owner Password credentials grant type. This would be exactly similar to your proposed implementation of having a api/token endpoint which can generate token.

However, using the OAuth semantics will help a lot. Think about extensibility. Say tomorrow you start to support other social login mechanisms. In fact, what if I want to use my existing OAuth compliant identity server. I can just create a new grant_type in system and done with authentication. You will get good support from existing client libraries as well. Ignoring the refresh tokens part will work, they are not needed for now. You can be implementing just 20-25% of the OAuth standard and still get support from ecosystem in terms of code and developer understanding.

For the session thing, you can use the same token as the session ID in the cookie. You need not inject the token through JS anywhere as it will automatically be sent with each request by browser. Some consideration for CSRF/XSS may be needed but can be figured out.

For remember me, you can have thing controlled through cookie itself. If remember me is selected, give an explicit expiry time to the cookie else don't give it. The session will be lost as soon as the browser is closed. You can also store the generation time of the token with in token table itself and then you can choose to keep extending the token life as API hits are received or have the hard deadline of 30 days or so from time of creation.

Also, store token in hashed format in DB for security purposes. Some fast hash though. It can't be as slow as the password hashing.

@tobyzerner
Copy link
Contributor Author

@designgrill

Say tomorrow you start to support other social login mechanisms. In fact, what if I want to use my existing OAuth compliant identity server. I can just create a new grant_type in system and done with authentication.

Thanks for bringing up single sign-on stuff — I hadn't thought about that, but it is quite important. That said, I don't understand how implementing an OAuth2 server would work as an authentication flow using external providers. Here's how I envisioned it would work (without OAuth2):

  1. User clicks "Log in with Facebook" button in Ember app.
  2. Ember app opens popup window with URL "http://myforum.com/connect/facebook" or whatever.
  3. Laravel app handles request to "connect/facebook" and redirects to Facebook login page with forum's client_id and client_secret.
  4. User enters their Facebook credentials and grants permission; Facebook redirects back to "http://myforum.com/login/facebook" or whatever with an authorisation code.
  5. Laravel app takes authorisation code, exchanges it for Facebook access_token, stores this in the DB associated with a new or existing user (according to email).
  6. Laravel app generates Flarum access_token for this user and renders it in popup, which sends it back to Ember app in parent window.

I'm just under the assumption that Laravel has to handle the whole external provider connection process because the client_secret needs to be kept secret. Unless I'm missing something?

For the session thing, you can use the same token as the session ID in the cookie. You need not inject the token through JS anywhere as it will automatically be sent with each request by browser. Some consideration for CSRF/XSS may be needed but can be figured out.

The API is stateless — it should not read any cookies. Therefore, the access_token must be sent in an Authorisation header.

For remember me, you can have thing controlled through cookie itself. If remember me is selected, give an explicit expiry time to the cookie else don't give it. The session will be lost as soon as the browser is closed. You can also store the generation time of the token with in token table itself and then you can choose to keep extending the token life as API hits are received or have the hard deadline of 30 days or so from time of creation.

I've implemented it so that the contents of the remember cookie is simply an access_token, then this is looked up by the Laravel app and any matching user is logged in. With regards to having expiry times on access_tokens, won't this cause problems with the remember cookie and third-party apps where you don't access your account in over a month?

Also, store token in hashed format in DB for security purposes. Some fast hash though. It can't be as slow as the password hashing.

Is this definitely necessary? If an access_tokens table was compromised, wouldn't the recommended thing to do be to just empty it? https://github.com/lucadegasperi/oauth2-server-laravel doesn't hash its access_tokens in the database, for example.

@designgrill
Copy link

Will first go through the easier things.

The API is stateless — it should not read any cookies. Therefore, the access_token must be sent in an Authorisation header.

At HTTP header level, both are not much different. You can write your API Server/Gateway to fall back to Cookie header if the Authorization is missing. There might be some check for CSRF needed here though. If CSRF proofing makes it complex, it can be dropped.

With regards to having expiry times on access_tokens, won't this cause problems with the remember cookie and third-party apps where you don't access your account in over a month?

Thats more of a product call rather than technical one. Third party apps, I assume, would be used to this fact that tokens may have expiry. They would just ask user to login again and get a new token. Fine in the name of security once a month or so.

If an access_tokens table was compromised, wouldn't the recommended thing to do be to just empty it? https://github.com/lucadegasperi/oauth2-server-laravel doesn't hash its access_tokens in the database, for example.

Say the access_tokens never expire. Now as these tokens are as powerful as passwords, getting them compromised is a big security risk as they will be usable immediately. Even if you identify the breach in a day, highly unlikely with the audience of flarum, the damage would already be done. If you use long tokens with a salt and store only the MD5, you avoid that risk. Sadly these tokens can't get same treatment as passwords because their hashing needs to be fast. Long token strings and a common salt may go a long way here.

Now for the whole OAuth thing. Feel free to discard this whole thing if looks overkill for flarum. This is based on some framework I plan to create in not so distant future. :)

Think how flarum mobile client is going to accept FB login. Its better that there is an API endpoint exposed (api.myflarum.org/token) which takes different type of credentials, does its verification, and returns the token. Now www.myflarum.org may just be a CLIENT of that API which will handle the browser redirects but ultimately pass on the stuff it got from Facebook to api.myflarum.org and gets the token which it can then decide to set in the cookie for instance.

@yulianovdey
Copy link

At HTTP header level, both are not much different. You can write your API Server/Gateway to fall back to Cookie header if the Authorization is missing. There might be some check for CSRF needed here though. If CSRF proofing makes it complex, it can be dropped.

They are very different because of the security issues associated with cookie authentication. Not having any fallback that reads credentials from cookies is the whole point :).

Now for the whole OAuth thing. Feel free to discard this whole thing if looks overkill for flarum. This is based on some framework I plan to create in not so distant future. :)

@tobscure I would suggest going with your strategy and just adding appropriate hooks that make authentication extensible for integrating flarum with OAuth or any custom provider.

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

No branches or pull requests