OAuth: How to hide API Secret Key from javascript

11,872

Solution 1

It's good to know which measures prevent which security holes.

You are correct that JavaScript is not well suited for encryption because there is no place to store a secret. There are also no good encryption libraries because you shouldn't be doing encryption in JavaScript.

The session key can serve as the authentication key. If you're using TLS your connection is secure and an attacker can't know the session key. Additionally, JavaScript doesn't need to know the session key. Cookies, by default, are sent with every request. And you can set the cookie to be an http-only cookie. You don't have to do this, but it does add another layer of security.

You can give the session cookie a very long expiration time so that it essentially works like a secret API key. The browser will take care of storing the cookie securely. It is advised to rotate the session key often, typically at the start of every new session and when authentication information changes (like a password reset).

CSRF-tokens prevent replay attacks. It's definitely recommend to secure a modification request with a CSRF-token. You don't need a CSRF-check for every request, just requests that modify sensitive information (such as your login credentials, or in your case: transactions). For CSRF-tokens you can use the same approach as the session key: store it in a cookie.

The key part is that JavaScript doesn't need to know about any of this.

One important thing that I'm sure you realize as well is that any keys or nonces you generate must be cryptographically safe. Don't use low entropy functions.

So:

  1. You don't need to encrypt the userid or email, TLS does that for you already. Additionally you can send the password as well, you don't need to send it separately in step 3. We're not going to do any encryption in JavaScript. All encryption is handled by TLS/HTTPS alone.

  2. If you have a separate authentication server (like a single sign on), this approach is fine. Else you can skip this step.

  3. You don't need this.

  4. The server doesn't need to decrypt anything, encryption is handled by TLS. How you store the password is a topic on it's own but I think you've got it.

  5. Ok. Again, the client shouldn't encrypt anything.

  6. Send just the session key. It's is enough.

Revised is:

  1. Client sends login credentials. Connection must be secure.

  2. Server verifies credentials and sends authentication token as cookie and keeps track of the authentication token is a session list.

For every request:

  • Client includes authentication token. This happens automatically if you use cookies.

  • Server verifies authentication token and possibly generates a fresh token that the client will use from then on.

Solution 2

Mobile apps should be considered as public clients. This means they should not store any secret. Whatever the encryption algorithm you will use, nothing prevent the client credentials from being compromised.

That is why the OAuth2 Framework protocol defines the Implicit grant type flow which allow public client interaction and do not need any client authentication. You may also consider the RFC7636 to protect the issuance of the access token.

Share:
11,872
ConnorU
Author by

ConnorU

Updated on August 11, 2022

Comments

  • ConnorU
    ConnorU over 1 year

    We're in the process of migrating our MVC-based server application and making a REST-ful API through which calls will be handled.

    I've been reading up on AES encryption and OAuth2 and decided to implement a solution grown form those concepts as follows:

    1. Client sends a request to log in providing a UserID or Email. This request is HMAC'd using an API Secret Key.
    2. The server checks if the UserID/Email matches an existing account and if it finds one, creates and stores a server nonce which it sends as part of the response to the client.
    3. The client creates their own client nonce and creates a new temporary key from the API Secret key and both nonces. It then sends a login request with a password encrypted using this temporary key [for added entropy and to avoid ever sending a password in plaintext].
    4. The server decrypts the password and HMAC using the latest nonce it has stored for this client on this platform [a mobile and a web client can have their own distinct nonces and sessions] and the client nonce which was sent in the clear, if the HMAC checks out it then validates the password against the database [PBKDF2 hashing and salting].
    5. If the request is valid and the password and UserID match records, a new Session Secret Key is created for that UserID on that platform and this Secret key is sent to the client and will be used to HMAC every API request fromt hat client henceforth.
    6. Any new non-login request would include an HMAC signature computed from the Session Secret key and randomized IV's.

    All communication is handled through TLS so this is added security and not the only line of defense.

    On the mobile apps this would work since you can hide the Mobile App's Secret Key on a config file and this gives some decent measure of security - [perhaps not a lot I'm not fully sure] but if we try to convert all the requests from our webpage to this form this would mean using Javascript to handle the client-side AES encryption and authentication and ... well as this article clearly explains, " if you store your API key in a JavaScript web app you might as well just print it out in big bold letters across the homepage as the whole world now has access to it through their browser’s dev tools."

    I could use only the nonces as the API Secret key -- or forgo using AES encryption for those requests altogether and try to validate through other means such as CSRF tokens and making sure all the requests come form our own front end in some way - but this wouldn't work if we wanted to create an API that allows integration with other pages or services and even then, how would I go about securing the client's secret Session key?

    The article suggests generating single-use cookies as a tokens but that's a limited solution that works for the poster's services but wouldn't for us. I want to be able to HMAC every request the user sends with a user-specific key that can expire and be reset and since the service will eventually handle money, I want request authentication to be locked down tight.

    So what are my options?

    Do I just ditch Javascript since it is doomed? Is there some way to store a secret key without exposing it clear as day hardcoded into the .js script? Should I generate a new temporary Secret key to be used for login calls only and send that to the user when they request the server nonce?

    Also, the post I linked to first suggests using a cookie to store the Session key for the client and then access the key from JS. Is this ok or would that provide more holes than it seals?

  • ConnorU
    ConnorU almost 8 years
    So you're saying if a client got a session key as a cookie and sends it there is no need for HMAC signatures? How would I protect against replay attacks with your revised authentication pattern?
  • Halcyon
    Halcyon almost 8 years
    The CSRF token prevents the replay attacks. It's a single-use token that is created by the server: 1. client requests /change_password 2. Server creates CSRF-token and stores it in a token list (with a reasonable expiration time), it includes the CSRF-token in the content of /change_password (for instance in the form action="/do_password_change?csrf=[token]". 3. Client sends CSRF token with request. 4. Server checks CSRF token and removes it from the token list. This prevents the replay attack because the 2nd use of the CSRF token will yield an invalid request (server knows it was used).
  • ConnorU
    ConnorU almost 8 years
    I'm gonna read up a bit mroe on CSRF. I want to wait before I mark your answer as accepted in case somebody has more input or alternative ideas to offer. Thanks!