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:

  • Client requests a URL (first request to the server)
  • Server gives the normal response plus some unique string (== session ID)
  • Client has to send this string with every request (which is done automatically using HTTP header)
  • Client logs in -> Server memorizes that this particular session ID is now logged in
  • Client visits a page which requires auth -> Nothing special to do, because the session ID will automatically get sent to the server via HTTP header
  • stateless auth token:

  • Client request URL (first request to the server)
  • Server just gives the normal response without any key or token or id
  • (nothing special here)
  • Client logs in -> Server creates an auth token and sends this token to the client inside the response
  • Client visits page which requires auth -> Client has to submit the 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:

  • Concerning sign_in security there's no difference between the current POST /users/sign_in and HTTP basic auth. Both use cleartext.
  • For sign_out HTTP basic auth has a disadvantage: Sign out is only possible closing the browser window
  • HTTP digest auth has a huge advantage: It doesn't transmit the password at all (just a hash of password plus random generated string)
  • (German) Wikipedia says: HTTP digest auth is not supported by all browsers. Maybe this information is way to old?!
  • What I need:

  • usernames and hashed passwords (bcrypt) stored in a database.
  • user can change his password and the password has not to be sent in plaintext. (The same problem occurs when it comes to user sign_up). Possible solutions?
  • of course: using SSL/TLS
  • client request a 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:

  • Create a XSRF token REST resource. So the (native) client has to request a XSRF token from this resource before it can do the first POST .
  • Disable XSRF protection entirely.
  • Questions:

  • How does XSRF protection work (in Rails)? How does the server know which token belongs to which client? The only way I can think of are sessions. This assumption leads to:
  • If I disable session in order to create a fully stateless REST API, XSRF protection won't work anymore. Right?
  • 4.: Stateless auth token

    Here I have mostly a lot of questions:

  • Does it have the same security problems as HTTP sessions? What I mean: Stealing the session ID has the same effect as stealing the auth token. Right?
  • Expiration of the auth token should work the same as with HTTP sessions: The server has to store somewhere (database respectively session) a timestamp and check that.
  • sign_out works the same, too?
  • Session: Destroy session on the server
  • Auth token: Destroy the token on the server
  • From what I've read it should be more secure to store the auth token inside the HTTP header (just like session ID), because server logs can contain GET parameters and thus could contain the token.
  • Should it just be a plain auth token or would it be better if the client also transmits its user_id or even the hashed password? HERE I read that the client should send:
  • user_id
  • expiration_date
  • a hash (or what's HMAC?) of [ 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:

  • Login
  • read HTTP Header of response to get the cookies
  • save all cookie data you need (especially the one with the session stuff) locally
  • send this session id with every request you do
  • Don't use sessions at all. From the point of view of a native client it's pretty much the same as 1.:

  • Login
  • Get some authentication token from HTTP Header or response body (it's your app, though it's up to you)
  • save this token locally
  • send this token with every request
  • 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:

  • User logs in at your API
  • Session get's created
  • Your users browser will have a cookie set with this session ID
  • Every request your user does do your API is automatically authenticated, because the browser will send all cookies (including the session id) along with each request to your API
  • And thus the attacking website simply has to do the following:

  • Write a custom HTML <form> which points to your API
  • Let the user somehow click the 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:

  • Use POST (or whatever method you like) and send the authentication information inside the request body. Or:
  • Send the authentication information within the HTTP headers. Because those information normally do not appear in the server log files. Or:
  • Have a look at the server config, and tell it to remove every URL parameter that is named "password" (or obfuscate is, so the URL becomes 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

    上一篇: ASP.NET Core中基于令牌的身份验证

    下一篇: 如何使用REST API进行身份验证? (浏览器+本机客户端)