There's a lot of misinformation in this thread, so I'm going to describe best practice for this sort of thing.
1. The client authenticates, and asks to be remembered.
2. The server generates a cryptographically random token of at least 128 bits in length. This token is *never* directly stored server-side.
3. The hash (or, possibly, a slow-hash) of the token is saved in the database along with a time of expiry
4. The non-hashed token is sent to the client as a cookie completely independent of the session cookie
When the client visits the website:
1. The server checks for the presence of the remember-me cookie
2. If the cookie is set, it is hashed and this hash is searched for in the database (and filtered to tokens with expiry times after now)
3. If the user is successfully logged in, the old token is deleted from the database. A new token is generated and sent to the client by the previously-described process
Now for a little bit of explanation.
This process completely isolates the authentication token from the session. If a token is somehow intercepted or discovered by an attacker, it is only usable for a single session. Such tokens should never be allowed to serve as permanent entry points into a user's account.
The token is never directly stored server-side. This confers two benefits. First, these tokens should be considered password-equivalent — if someone manages to steal your table of remember-me tokens, they would be completely unable to use them to log into a user's account. Second, this prevents timing attacks; if you simply looked for matching tokens in your database, an attacker can (shockingly simply) use timing information to guess a user's remember-me token in O(n) time.
I don't see why you have to store the hash of the token. You know the key you used for HMACing, why can't you just check that the cookie contains a valid hash of the rest of its data (which is as extensive as you need: session id, expiry, IP, whatever)? To prevent the use of old sessions just make the session id a counter. A single session counter per user is probably less hassle (and more useful) to store than the whole hash.
To avoid the problem of storing the token's hash, you have introduced:
* parsing a structured cookie, vs a meaningless 128-bit string
* securely storing and managing a secret HMAC key (which may be used to forge or modify credentials)
* securely verifying an HMAC using a constant-time string comparision
So what, exactly, is the benefit of your proposal over mine? You've removed the (useful) distinction between an authentication token and the user's session. You've introduced significant additional complication and moving components. And you've increased the attack surface for security vulnerabilities. For what?
Doing all that to store a 32-bit integer is somehow less hassle than hashing and storing a 128-bit string?
you have introduced: parsing a structured cookie, vs a meaningless 128-bit string
You're going to need some logic for dealing with session, user, etc. metadata. Stipulating that parsing metadata out of a cookie is in some sense harder than reading it from a DB, keeping it in the database requires more storage (in addition to the 128-bit hash), more syncing among the various masters and slaves, and higher security. (An attacker who gets a DB dump can't see account metadata that isn't stored in the DB. Sure I'm storing a counter but what can he do with that?)
securely storing and managing a secret HMAC key
How do you run an online service without doing key management? Whatever techniques you use for your other keys, use them for this key too. It isn't as if we need a separate key for each user.
You've removed the (useful) distinction between an authentication token and the user's session.
I don't understand this claim. If I want to generate a new token for the current session, what's stopping me?
And you've increased the attack surface for security vulnerabilities.
Because HMAC collisions are easy now? I've moved some things around, but I'd say there is a tradeoff between the CAP costs of the "store everything" technique you propose and the slightly more involved validation process I describe. It's cool if you prefer your method for your situation, but can't we admit that "I'm going to describe best practice" was a bit overstated?
When you get the details wrong? Relatively so. And even experienced people get this shit wrong more often than they get it right. Rails had this vulnerability. Google's KeyCzar had this vulnerability. The average, non-crypto-enthusiast has effectively zero chance of getting this right out of the box.
Comparing server-computed hashes is inherently safer and less error-prone than comparing client-controlled HMACs.
The only difference between my description is I do not store the username with the cookie; it is unnecessary and confers no discernable security benefit. I also avoid associating multiple remember-me tokens with a user, so an attacker who intercepts one must use it before the user next logs in.
Is your approach wrong? No. But it is more difficult for the average programmer to do securely and correctly, and confers few (if any) real benefits.
This process completely isolates the authentication token from the session. If a token is somehow intercepted or discovered by an attacker, it is only usable for a single session. Such tokens should never be allowed to serve as permanent entry points into a user's account.
The token is never directly stored server-side. This confers two benefits. First, these tokens should be considered password-equivalent — if someone manages to steal your table of remember-me tokens, they would be completely unable to use them to log into a user's account. Second, this prevents timing attacks; if you simply looked for matching tokens in your database, an attacker can (shockingly simply) use timing information to guess a user's remember-me token in O(n) time.