Static Credentials Must Not Be Used In The Browser

Credentials / puuikibeach

Authentication is described in this post.

Modern business web applications tend to be a collage of service calls to numerous different back end services. Sometimes, (1) you control those services — most applications have some type of back-end. Let’s assume that your application uses OIDC + OAuth2 for the time being to authenticate users, secure and track the user session, and invoke these back-end services — if this assumption is incorrect, read the advice below. Sometimes, (2) someone else within your organization offers services that are being used — the sociopolitical situation within said organization regarding operational control, ease-of-use, and inter-team cooperation will vary widely. Sometimes, (3) third-party APIs are being utilized. In the second and third cases, one can commonly find services being called that are only issued a set of static credentials such as API Keys, OAuth2 Client Credentials, or a Service Account.

Anti-Pattern: Calling APIs Directly From The Browser With Static Credentials

I call these static credentials because they are always the same until you change periodically (or never). These credentials are always the same, regardless of the user involved. This means that the end user’s security context ends at the browser; this makes an effective / secure Fine Grained Authorization decision based on the end user’s identity nearly impossible. Compare this to something such as an OAuth2 Access Token that has been issued using the OpenID Connect (OIDC) Authorization Code Flow and is tied to the user’s authenticated session. That credential is uniquely tied to that authenticated session and by extension to that user.

In the “Understanding OAuth2” post, we introduced the OAuth2 Public Client versus Confidential Client concepts. Basically, the Confidential Client can securely store a secret, the Public Client cannot. Any Javascript applications running within the browser (the user-agent-based application) are by definition Public Clients. All that static content including Javascript that is downloaded is published on a publicly accessible website (or at least accessible within your organization); so, anything that is in it that code is also publicly accessible including our secrets. You could play games with obfuscating it, but in the hands of a skilled attacker any obfuscation mechanism that works in this context could be reversed. A similar situation exists with native desktop applications and native mobile apps.

This ties into Business-to-Business (B2B) integration patterns, by providing a guideline for deciding which integration pattern to use in that space. Do not integrate directly with third-party services from a web browser if you were only issued static credentials. If the third-party service supports OAuth2 or OIDC and you can seamlessly integrate with their IdP through Federated SSO, great, go for it.

A Javascript application running in the browser can never securely store or use a client secret. In the similar way, any Javascript application running in the browser can never securely use an API key or a service account username and password. Likewise, none of these static credential types can be used securely in native desktop applications or native mobile apps.

We’ll call all of this an identity anti-pattern. That just might be a new series on this blog.

How Do We Fix This?

Depends on the situation.

For starters, refer to my earlier blog post on the topic of “When To Use Which (OAuth2) Grants and (OIDC) Flows”. Also, check out “OAuth2 Access Tokens vs. API Keys — Using JWTs”.

In all three cases mentioned above (OAuth2 Client Credentials, Service Account Credentials, or API Keys being used in the browser), some type of back-end system that you (or your organization) control and is properly secured should serve as an intermediary (proxy) for the request that needs these credentials. This back-end intermediary should be responsible for securely storing the credentials and injecting the credentials into the request before sending it to the target service. This intermediary could be an API Gateway, an application server, a microservice, a serverless function, etc.

Abstracting Static Credentials Away From The Browser

A mechanism must be used to maintain the user’s security session between the browser and the backend — session cookie, OAuth2 Access Token tied to the user’s session, etc. The intermediary replaces the incoming security tracking mechanism with the outgoing static credential for the third-party API — the translation of the front-end security credential with the back-end security credential. That is a classic API Gateway security integration pattern that I discuss here and in other posts.

Stated another way, the integration point is the back-end service that contains the proxy, not web browser (user-agent). Similar results could be accomplished other ways, but all will involve abstracting the static credentials away from the browser to a secure back-end in some manner.

Further, note, that the proxy (intermediary, integration point) described above is NOT a substitute for an organization’s forward or internet egress proxy for web traffic.

Other Cases

The same advice can be applied to native mobile apps and native desktop applications. The code bundle or executable is not capable of securely storing the credential.

Some platforms routinely use API keys from the browser for tracking purposes and metrics gathering. In general, this is relatively harmless; though, it must be called out that it is possible for a malicious actor to modify your metrics and telemetry. As an example, see here for Google’s guidance for the use of API Keys with Firebase.

In the event of an unauthenticated call being made; so, no credentials being used, review the advice given here — I’m including this for completeness.

Sometimes, you will see mobile apps that use an API key that is hard-coded in the application to invoke APIs (its own or third-party). I had a situation once where this very practice led to the compromise of the key and a third-party mobile app was using the key. The mobile app updated the key, the third-party mobile app team extracted it and did a release a couple of days later. Lawyers got involved in that one.

It’s a fairly common pattern to see mobile apps use API keys to call APIs and simply rotate the key every few weeks either out of a belief that this either addresses the situation in the last paragraph or increases overall security. I don’t recommend this approach.

Putting the API key or other static credential into a configuration file that is downloaded separately from code bundles from an obfuscated path technically means the credential is no longer part of the executable, code bundle, or static content, but all you are doing is adding an extra moment or two that it takes a skilled attacker to find the configuration file. So, this approach isn’t recommend either.

Summary

Do not use the following directly from javascript code running in the browser:

  1. API Key
  2. OAuth2 Client Credentials (whether you are going to use it for the OAuth2 client Credentials Grant, OIDC Authorization Code Flow, or something else).
  3. Service Account username and password.
  4. Any other type of static credential that one may encounter.

These are all variations on the same bad idea — anti-pattern.

Instead, the intermediary proxy that has been properly secured (described above) should be used to inject the static credential into the request.

If your web browser-based front-end (or mobile app, or native desktop application) only offers one of these static credential types to its backend services for authentication purposes, you need to reevaluate your identity architecture. It is trivial to circumvent this security control.

Leave a Reply