7

Imagine you're running a service that implements an OAuth 2.0 flow to allow your end users to log into 3rd party apps, and authorize those apps to consume your service's data through some API.
Upon successfully authenticating a 3rd party app through your OAuth flow, your service sends the 3rd party app a bearer token that authenticates (and authorizes) the 3rd party app (on behalf of the end user) to use your service's API.

Such a bearer token needs to be stored on your servers, in order to perform authentication for incoming API calls. Since the bearer token authenticates (and authorizes) clients, you need to store it safely, like you would store a password.
However, while passwords are salted + hashed before saving them to disk, they are also looked up by their corresponding principal (username/email-address), rather than by the secure representation of the password.

In case of a bearer token, there is no principal to use for looking up matching credentials/tokens. Assuming you'd want to store bearer tokens like passwords (salted + hashed), you'd have to look up every possible salt, apply each one, then hash the salted token, and then check if it exists anywhere in your system. That doesn't scale.

So my question is: How can you safely store bearer tokens on your server? That is, without having to generate every possible secure representation of it whenever you need to verify a token.
By "safely store bearer tokens", I mean that an attacker who obtains the file of all "safely stored" bearer tokens won't be able to use them.

derabbink
  • 241
  • 2
  • 7
  • The purpose of salt is not to defend against external threats over the wire, it is to defend against threats where the database has been compromised. The salt protects against the use of rainbow tables to find inputs that would result in hash clashes. The entropy of the input is irrelevant if it has been read directly from your db. Salt your hashes, always. – Pete Sep 12 '16 at 22:57

2 Answers2

3

If requests from the third party to the OAuth provider have some other data that associates the request to the user, for example the user's name or id, the server could store the bearer token like a regular password with individual salts. However, if the third party requests don't have a piece of identifiable information, the server could store a salted+hashed version of the bearer token with a shared salt for all bearer tokens. Because OAuth tokens expire quickly a unique salt isn't nearly as important compared to a password which might never expire. If the token expires after 6-12 hours the attacker can't realistically crack the hash and use it to access a user account. The server could then periodically rotate the salt every week or so for additional security.

Justin Moore
  • 769
  • 4
  • 9
  • 1
    There is indeed no identifying information (like a user ID) being sent along with the token. Your assumption that tokens are short-lived isn't true though. They may be valid for a month. – derabbink Jul 22 '15 at 06:25
  • How long are the generated bearer tokens and are they random data that's base64 encoded or just random printable characters? – Justin Moore Jul 22 '15 at 19:31
  • Tokens consist of 200 alphanumeric (`[A-Za-z0-9]`) characters – derabbink Jul 22 '15 at 21:33
  • 2
    Honestly, you could just hash it without a salt and use a strong hashing algorithm. If the database containing the tokens was compromised the token isn't immediately useful to the attacker and cracking the hash for the token is impossible because the complexity is huge. – Justin Moore Jul 23 '15 at 15:51
2

Based on Justin's answer, I'm constructing my own.

If the tokens are long enough (in my case they are 200 alphanumeric ([A-Za-z0-9]) characters, there is no need to salt them. Hashing alone should then be enough, since the complexity is high enough.

If tokens are not long enough to get away with hashing only (or if you want to insist on salting them as well), you could modify the token generation scheme slightly: Instead of generating a random token, also generate a token-specific principal. Then concatenate the two and hand the result to the consumer app as a single token. That's essentially asking API clients to send user-identifying information as well, but it's conveniently embedded in a single token.

Internally, you salt + hash the actual token before storing, and store the token-specific principal separately. When it comes to verifying a token, you split the submitted token in two, getting back the actual token and the token-specific principal. This principal can then be used to look up the salted+hashed actual token against which you can run a matcher.

Concrete example:

  1. Generate a bearer token: e.g. a0z9B1Y8c2x7
  2. Generate a fixed-length token-specific principal: e.g. D3W6e4v5F5
  3. Generate a salt, e.g. aabbccddeeff00112233445566778899
  4. Salt+hash the bearer token (a0z9B1Y8c2x7), which would result in something like this: xyzXYZabcABC
  5. store the salted+hashed token alongside the token-specific principal, i.e. store a tuple (xyzXYZabcABC, D3W6e4v5F5)
  6. Give the concatenation of the token and the principal to the user, to treat as a regular bearer token: a0z9B1Y8c2x7D3W6e4v5F5
derabbink
  • 241
  • 2
  • 7
  • 1
    To whomever down-voted this: Congratulations on being completely non-constructive. At least make an attempt to add a comment explaining what you don't like about it. – derabbink Sep 13 '16 at 08:21