I'm implementing a REST service that requires authentication. I cannot store any per-user state (such as a randomly-generated token) because my service does not have direct access to a database, only to another backend service.

The solution I came up with is creating a JSON Web Token (JWT) when the user authenticates. The JWT claims set contains the user ID in the Subject ("sub") field. The server then encrypts the claims set directly ("alg":"dir") using AES GCM with 256 bit key ("enc":"A256GCM") creating a JWE. The key is generated once when the service starts and stored in memory.

To authenticate, the client submits the username/password, and the server responds with the token described above. The client then sends that token with each subsequent request.

When the client submits the token with subsequent requests, the server decrypts it using the key, and assumes the user ID in the "sub" field to be the ID of the current user, without any further authentication checks. Token expiration is handled by the "exp" field in the JWT claims set.

The connection between the client and the server will use SSL/TLS, so the token will not leak.

I'm using this library to create and read JWTs as I don't trust myself to write correct cryptography code.

My questions:

  1. Is the above approach secure? Can an attacker impersonate another user by manipulating the token?
  2. Is the approach over-complicated? Would using MAC (in other words: JWS) instead of encryption have the same security? (or possibly more, since it's simpler and there's less chance of making a mistake). There's nothing particularly secret in the JWT claims set, and the user knowing their own ID doesn't matter.
  3. Is my choice of JWE algorithm and encryption appropriate?
    • For the JWE "alg", the library I'm using supports direct encryption (using the key directly to encrypt the claims set) and RSA (generating a new key to encrypt the claims set for each token, and encrypting the generated key with an RSA public key). I chose the former because it's easier to generate a symmetric key than an RSA key.
    • For the JWE "enc", the library supports AES GCM and AES CBC HMAC SHA2 (with various bit lengths). I chose GCM arbitrarily.
up vote 49 down vote accepted

Your basic approach is valid: generate the JWT when the user logs in, expect subsequent messages to carry the JWT, trust the subject field in the JWT in those subsequent messages if the JWT is valid. But there are several things you should be aware of:

  1. As Daisetsu say, you could use a MAC ("alg":"HS256") as MACs are specifically designed to prevent alteration of the payload, while encryption algorithms typically (counter-intuitively) are not. However since you're specifically using AES in GCM mode, you already get tamper-resistant encryption ("authenticated encryption"), so that's not really a concern.
  2. When validating an incoming JWT, be careful what you consider valid. For example, I could call your service with {"sub":"me","alg":"none"} and while that JWT is valid in some sense, it isn't something you want to accept.
  3. Since JWT is a draft, not a standard yet, it might change. If it changes enough, the library you're using might have to change in ways that break compatibility with your code.
  4. If you can't store any server-side state, you have no way to invalidate the JWT when the user logs out. In effect your service has no logout function, which may be a security problem especially if you set the expiration time too far in the future.
  5. If you set the expiration time too soon, you may have a problem with users still being logged in but not having a valid JWT. This may lead to awkward error-handling and user workflow issues.

Since you said your server has no access to a database, I assume the actual login is handled somewhere else, perhaps the backend server you mentioned. You didn't say how your server knows that the user just logged in. Depending on user perception of the relationship between your service and the thing they know they logged into, the last two points above might be moot.

  • 2
    GCM is an authenticated encryption mode, so it does provide authentication. – Dietrich Epp Sep 16 '14 at 19:34
  • regarding your #4, doesn't deletion of the token on the client-side constitute a logout in a stateless server scenario? – Dan Jun 2 '15 at 19:24
  • 1
    You cannot trust the client side. If someone writes down that token (or it leaks somehow), he can go to another machine and have immediate access -- no need to log in again. That may or may not be a problem, depending on your requirements. – Neeme Praks Sep 18 '15 at 20:22

If none of the data is sensitive, then all you need to do is preserve the integrity of the data. Signing (JWS) the token should be enough to do that.

If you're just doing a signature you should be fine with HMAC SHA-256. Remember to set an expiration of the token, and to check whether the user manually logged out (in that case, invalidate the token). Once you take expiration and logout into account there's not all that much to worry about (algorithm wise). The chances of someone cracking a SHA-256 in the time of a single session (should) be relatively low (assuming you require re-auth at a REASONABLE interval).

As always with signing, make sure you provide the content to be signed (username, account type, etc.) don't ever let any of the user defined data be signed or you may be in a dangerous situation.

Disclaimer: This post is strictly my opinion. I'm not making any claims about the reliability or fitness of my answers. You should always consult with a security professional to discuss your specific security implementation concerns. This is purely educational.

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.