I have a single page React application. I'm trying to authorize against Salesforce. I created a connected app inside of Salesforce and have a client_id and client_secret.

I originally tried using the library oidc-client-ts to handle my authentication flow. However, every example I can find, shows them explosing the client_secret on the client.

It seems every resource on single page authentication is handling it wrong. What is the point of it being a secret if we are supposed to just place it directly in our front end code?

Shouldn't the process be:

  1. Request code from identity provider with pkce and redirect uri
  2. Identity provider generates code and sends it to the redirect uri
  3. Redirect uri is a server-side endpoint that takes the code, and exchanges it for an authentication token by calling the identity provider with the code and client_secret
  4. The server then receives the authentication token and redirects back to the client application

What am I missing?

2

There are 2 best solutions below

0
On

PUBLIC CLIENTS

The oidc-client-ts library is a pure JavaScript SPA solution, for SPAs that do not use a client secret or backend client. When using it the SPA is a public client. A standards-based authorization server should support this option, but some identity systems, eg Salesforce, may not.

BACKEND FOR FRONTEND

To integrate with SalesForce you need to store the client secret in a backend component, as you indicate. There is nowhere secure to store the value in a browser.

Using a Backend for Frontend is considered a more secure flow these days, to limit the impact of XSS exploits by avoiding exposing the API credential (access token) to JavaScript code.

Such a backend can safely store a client secret. Yet this requires a more complex flow than that used by oidc-client-ts, due to an extra application cookie layer. They should be considered two distinct solutions.

0
On

From here:

The most common and secure OAuth flow is the authorization code flow. Unfortunately, that process relies on apps providing a client_secret in the final request for an access token. For certain types of apps, that makes leaking the secret an inherent, unavoidable risk, which is why they instead needed to rely on an implicit flow. These apps include, but are not limited to mobile apps, single-page apps (SPA) in JavaScript, and serverless apps.

Because these are public clients, there’s no way for them to guarantee the security of the secret used for the token exchange. Using the implicit flow solves for that, but with the added risk of exposing the access token in the redirect URI at the end of the authorization flow, which makes the flow vulnerable to different types of network and malicious app interceptions.

This tradeoff was acceptable (and necessary) back when apps couldn’t make cross-domain requests—making it impossible to complete an authorization code flow. However, now that CORS is widely supported, there’s no need for that historical compromise; apps can make direct post requests to the token endpoint.

Without the cross-origin problem, public clients can take advantage of the authorization code flow by using PKCE, which works by substituting the static client secret with a string that is dynamically generated. By doing so, the PKCE flow eliminates leaky secrets while allowing the authorization server to verify that the app requesting the access token is the same one that initiated the OAuth flow.

When a user kicks off a PKCE authorization flow in your app, here’s what takes place:

1- Client (your app) creates the code_verifier. (RFC 7636, Section 4.1)

2- Client creates the code_challenge by transforming the code_verifier using S256 encryption. (RFC 7636, Section 4.2)

3- Client sends the code_challenge and code_challenge_method with the initial authorization request. (RFC 7636, Section 4.3)

4- Server responds with an authorization_code. (RFC 7636, Section 4.4) 5- Client sends authorization_code and code_verifier to the token endpoint. (RFC 7636, Section 4.5)

6- Server transforms the code_verifier using the code_challenge_method from the initial authorization request and checks the result against the code_challenge. If the value of both strings match, then the server has verified that the requests came from the same client and will issue an access_token.