How to do authentication with a REST API right? (Browser + Native clients)
I'm building a web application using Rails. At the moment I'm using Devise with HTTP sessions which was pretty easy to set up and it's working well.
The application consists of one URL providing an AJAX web application. The rest of the URLs available belong to the REST API. So everything and every little data request is done via AJAX.
Now I'd like to extend the whole thing to support native clients. I read a lot about stateless auth, http basic and digest auth, http sessions, cookies, xsrf, etc... And now I feel like I can't have a secure app, because there's always a way to hijack some parts of it.
1.: HTTP session Vs. stateless auth token
What's the difference? I don't get it.
HTTP session:
stateless auth token:
For me both ways look pretty similar. With Rails I can also choose to store the session inside the database... Devise would do the same with the stateless auth token.
2.: The authentication method
Right now I'm using POST /users/sign_in
with {"user":{"email":"e@mail.com","password":"p455w0rd"}}
.
But there are other possibilities like HTTP basic auth and HTTP digest auth, but also solutions like oAuth (too big for my purpose).
From what I've read:
POST /users/sign_in
and HTTP basic auth. Both use cleartext. What I need:
want_to_change_password_salt
and uses it to encrypt the password on client side. but (?!) this way I'd sent an essential part of the hashed password over the wire plus the hashed password. Sounds insecure to me?! 3.: CSRF Token
As said above, right now I have just a normal AJAX website using the REST API. It has XSRF protection: The website gets delivered by rails and thus has embedded the XSRF token. I read it using AJAX and transmit it when doing a POST
. Rails then returns the requested data and a new XSRF token, which I then use for the next POST
.
Now I want to change my server application to work with native clients. A native client won't load the HTML page and thus won't retrieve a CSRF token. So the following options came to my mind:
POST
. Questions:
4.: Stateless auth token
Here I have mostly a lot of questions:
GET
parameters and thus could contain the token. user_id
expiration_date
user_id
, expiration_date
, SECRET_KEY
]. Where SECRET_KEY
is basically a random string generated by the server. Sorry for the huuuge post, but security is essential! And I don't want to make design mistakes which could probably expose private data.
Thank you :)
Here's a bit of new information and new questions ;-)
:
5.: Native Clients
As far as native clients are concerned, there's no (easy) way to use sessions:
A native client is no browser
Thus it won't easily handle cookies (and without cookies there's no typical session handling)
So there are 3 possible choices:
Implement session handling for native clients. This would be like:
Don't use sessions at all. From the point of view of a native client it's pretty much the same as 1.:
The hybrid approach. This basically means, that the server has to distinguish between browser and native client and then check the provided session id and session data or (for native clients) check the provided auth token.
6.: CSRF Token with stateless (= no session/no cookies) auth
CSRF Protection protects your users from malicious websites, that try to do some request on your API in the name of your logged in user, but without your user knowing it. That's pretty simple when using sessions:
And thus the attacking website simply has to do the following:
<form>
which points to your API Submit
button Of course this form will be something like:
<form action="http://your.api.com/transferMoney" method="post">
<input type="hidden" name="receiver" value="ownerOfTheEvilSite" />
<input type="hidden" name="amount" value="1000.00" />
<input type="submit" value="WIN MONEY!!" />
</form>
This leads to the following assumptions :
CSRF Protection is only needed because browsers automatically send cookies.
Native clients to not need CSRF Protection (of course: your browser can't access the authentication data (token, cookie, whatever) of your native app, and your native app won't use a browser to communicate with the API)
If you've got an API design which doesn't use Cookies to authenticate the user, there's no possibility to do CSRF. Because the attacker must know the authentication token and explicitly send it along with the malicious request.
If you want to oversecure your app, you can of course use CSRF Tokens along with you stateless authentication mechanism, but I'm pretty sure, that there's no additional security gain.
7.: The right HTTP Methods to choose
Login / Sign in and Logout / Sign out:
Never use GET
for (at least) three reason:
CSRF Protection in most cases only protects POST, PUT, PATCH and DELETE and thus a CSRF could login a user without his knowledge when using a GET request
GET requests should never change the application state. But when ie using Sessions the application state changes on login/logout, because a session gets created or destroyed.
When using a GET request and transmitting the authentication information as URL parameters (ie http://your.api.com/login?username=foo&password=bar
) there is another problem: Server logs! Most servers simply log every HTTP request including all URL parameters. That means: If your server get's hacked, there's no need to crack the password hashes from your DB, they must just have a look at the server's log files. In addition a malicious admin could also read the login information for every user. Solutions:
login?username=foo&password=***
inside the logs). But I suggest, to simply use the request body for this kind of information along with the POST method. So you could use for example:
POST http://your.api.com/authentication
for login
DELETE http://your.api.com/authentication
for logout
8.: Passwords and Hashing
Authentication only works with some secret key. And of course this key should be kept secret. This means:
Never store a password in plaintext in your database. There are several libraries available to make it secure. In my opinion the best option is bcrypt
.
bcrypt : It's been optimized to hash passwords. It automatically generates a salt and hashes the password multiple times (rounds). In addition the generated hash-string contains everything needed: Number of rounds, salt and hash. Though you just need to store this one String and there's no need to write anything by hand.
of course you can also use any other strong hashing library. But for most of them, you've got to implement salting and using more than 1 rounds yourself. Additionally they wont give you just a single string like bcrypt does, though you've got to manage yourself to store rounds, salt and hash and reassemble it afterwards.
rounds : This is simply how often the password should be hashed. When using 5000 rounds the hashing function will return the hash of the hash of the hash of the hash of the password. There's basically a single reason to do this: It costs CPU Power! This means: When someone tries to bruteforce your hash, it takes 5000 times longer when using 5000 rounds. For your application itself it doesn't matter that much: If the user knows his password, he will not recognize, if the server took 0.0004ms or 2ms to validate it.
good passwords : The best hashing function is useless, if the password is too simple. If it can be cracked, using a dictionary, it doesn't really matter if you hashed it with 5000 rounds: It will maybe take a few hours longer, but what are a few hours, if it could be months or years? Though make sure, that your user's passwords contain the usual recommendations (lower + upper case + numbers + special chars, etc. pp.)
9.: Sending encrypted passwords over the wire
If you can't (or don't want to) rely on HTTPS, but don't want to send passwords in cleartext when signing in, you can use asymmetric cryptography ( http://en.wikipedia.org/wiki/Public-key_cryptography ).
This server creates a key pair (public key and private key). The public key is made available to the clients, the private key has to be kept private!
The client can now encrypt data using the public key, and this data can only be decrypted by the owner of the private key (= the server).
This should not(!) be used to store passwords in the database, because if your server gets hacked, the hacker will have the encrypted passwords and the private key for decryption. Though keep using some hashing algorithm (like bcrypt) for storing passwords in your database. Another reason is, that you can easily generate a new key pair, if you think that someone cracked you encryption.
HTTPS basically works the same way. Though, if your application uses HTTPS (which is recommended) there might be no big benefit in terms of security. But as stated above, if you can't use HTTPS for whatever reason or don't trust it, that's a way to craft your own secure connection.
And keep in mind that a real HTTPS connection encrypts the whole(!) connection and all data, not only password data. And it encrypts it both ways, from client to server and server to client.
链接地址: http://www.djcxy.com/p/506.html