authts/react-oidc-context

Concerns Regarding `client_secret` Exposure in Client-Side Applications

swiiny opened this issue ยท 17 comments

description

I have identified a potential security concern related to the handling of the client_secret within a client-side application. Currently, the client_secret is included in the client-side code, making it accessible from the user's browser. This practice may expose us to security risks, including unauthorized access and potential misuse of the client_secret. I aim to assess the associated risks and explore more secure alternatives if necessary.

The client_secret is a sensitive piece of information intended to authenticate the client application to the server, ensuring that requests made to the server are from a trusted source. In traditional server-side applications, the client_secret is securely stored on the server and is not exposed to the public. However, in our case, the client_secret is included in the JavaScript bundle sent to the client, making it accessible to anyone who inspects the browser's resources.

Potential Risks

  1. Exposure to malicious users: Malicious users can easily extract the client_secret from the browser, potentially using it to impersonate the client application.
  2. Security compliance issues: Storing the client_secret client-side may violate security compliance requirements or best practices, potentially leading to legal or reputational repercussions.

Questions and Next Steps

  1. Risk Assessment: What is the level of risk associated with exposing the client_secret in the client-side code? Are there any documented incidents or breaches attributed to this practice?
  2. Technical Solutions: Can we implement any technical solutions that allow us to customize the https://<provider>/token endpoint to call our backend instead of doing it client side?

Very true, in case you can not protect a client_secret, e.g. when running on a client on customer side like browser. Do not use client_secret. The preferred way to use is using the PKCE code flow....

Thanks for your answer @pamapa!

I initially questioned the necessity of keeping certain secrets private, given the library is solely for browser use, suggesting perhaps those secrets might not require stringent protection. However, a significant challenge has arisen: the authentication provider in question does not support PKCE code flow. This absence necessitates an alternative method to securely authenticate users, hence my proposal to override react-oidc-context library's default <provider>/token endpoint call.

To address this, I propose the implementation of a function capable of:

  • Waiting for a custom function's execution: This custom function should perform the necessary actions to acquire an access_token from the API.
  • Returning the access_token: Once obtained, this token would then be used to ensure secure interaction with the frontend web application.

This approach is particularly vital for the application I'm developing, aimed at a marketplace. It requires that users authenticate with the original application to obtain an access token. This token, in turn, enables my application to securely interact with the parent application's API, ensuring a secure and seamless integration.

the authentication provider in question does not support PKCE code flow.

This library and the underlying library has a strong focus on PKCE code flow, respectively on OAuth2.0. I do not intent to support anything else.

BTW: even if you acquire an access_token from the API within the browser context you are exposing it to a potential attacker.... even if it is only part of the memory for a very short time.

React apps are usually assigned a "public" client which does not require a client_secret, and instead relies on PKCE code flow mentioned like @pamapa said.

Now... @swiiny I ran into a similar situation years ago on a project where the identity provider did not support PKCE code flow. Our workaround then was to add a "pass through" api to one of our backend services. It essentially intercepted the request from the React app, then added the client_secret to the request and forwarded it along to the identity provider.

@pamapa (or @zach-betz-hln ) I was just about to ask a similar question but saw this one, so I figure I'd jump in: I'm looking to integrate my React app (using this library) with Keycloak using Authorization Code with PKCE. I was able to get a basic integration flow with Keycloak working using the code examples in your READE (so thank you!). Does this library use PKCE by default (meaning do the code examples in your README essentially create an Authorization Code w/ PKCE flow by default) or are there other configurations/code I'd need to implement to leverage PKCE? Thanks again!

Does this library use PKCE by default (meaning do the code examples in your README essentially create an Authorization Code w/ PKCE flow by default) or are there other configurations/code I'd need to implement to leverage PKCE?

Yes, best practice (Authorization Code w/ PKCE) is the default.

Yes, best practice (Authorization Code w/ PKCE) is the default.

Thanks @pamapa , my understanding was that, with PKCE, when the user authenticates, a hashed Code Challenge is sent to the authorization server, along with the Hashing Method (SHA-256, etc.). And then, on each subsequent request for a token, the client sends over a Code Verifier, which is the plaintext version of the hashed Code Challenge. If the server supports Authorization Code w/ PKCE, it will hash the Code Verifier with the indicated Hashing Method and compare it to the Code Challenge. If they match, the token request is granted, etc.

Where and how do I set these (the Code Challenge and Code Verifier) in react-oidc-context?

Where and how do I set these (the Code Challenge and Code Verifier) in react-oidc-context?

They are part of the underling library. Have a look at oidc-client-ts. e.g. https://github.com/authts/oidc-client-ts/blob/main/src/utils/CryptoUtils.ts

Thanks again @pamapa , so just verifying, it looks like there's nothing I need to explicitly set, this library will generate and send Code Challenges/Verifiers automatically for me using oidc-client-ts?

Thanks again @pamapa , so just verifying, it looks like there's nothing I need to explicitly set, this library will generate and send Code Challenges/Verifiers automatically for me using oidc-client-ts?

Yes! For you to verify:

  • ensure response_type is not set (default is "code") or explicitly set to "code"
  • ensure you are not using client_secret

In case you want to see the process you can enable logging like described here: https://authts.github.io/oidc-client-ts/#md:logging

React apps are usually assigned a "public" client which does not require a client_secret, and instead relies on PKCE code flow mentioned like @pamapa said.

Now... @swiiny I ran into a similar situation years ago on a project where the identity provider did not support PKCE code flow. Our workaround then was to add a "pass through" api to one of our backend services. It essentially intercepted the request from the React app, then added the client_secret to the request and forwarded it along to the identity provider.

Indeed it's a client only library so I was surprised to see the ability to provide the client_secret directly. It could be a good idea to add a warning in case of someone not aware about the risks try to use this package.

I'll check to implement a pass through API as you suggested or will build my own solution based on oidc-client-ts directly so it can be used within Nextjs applications and its server actions.

@swiiny out of curiosity, what identity provider are you using that doesn't support Authorization Code w/ PKCE flow ?

@swiiny in case it helps #1208

@zach-betz-hln it is with this API https://docs.bexio.com/

Thanks for #1208! ๐Ÿ‘€

I see what you mean...

From their Authentication docs in the Authorization Code Flow section:

  1. The user clicks Login within the regular web application.
  2. The web application redirects the user to the /authorize endpoint of the bexio OpenID Connect service.
  3. The bexio OpenID Connect Service displays the login page.
  4. The user authenticates and sees a consent page listing the permissions bexio will give to the web application.
  5. The user is redirected back to the web application with an Authorization Code.
  6. The web application sends this code to the bexio OpenID Connect service (/token endpoint) along with the application's Client ID and Client Secret.
  7. bexio verifies the code, Client ID, and Client Secret.
  8. An ID Token and Access Token (and optionally, a Refresh Token) is returned to the web application.
  9. The web application uses the Access Token to call the bexio API.
  10. The bexio API responds with requested data.

For this step:

  1. The web application sends this code to the bexio OpenID Connect service (/token endpoint) along with the application's Client ID and Client Secret.

In order to remove the client_secret from your React code, that's where you may need to develop a custom pass-through API to workaround bexio's implementation.

Roughly:

  • Your React app calls your custom /token API
  • This API appends the client_secret to the request, then forwards it to bexio
  • bexio responds, then your API forwards this response to your React app

Hey @zach-betz-hln, thanks for your answer!

Being able to override the default call to /token and make it pass through my own api is my original request through this PR. This is where the issue starts basically, I want to use this package as it comes with many abstractions and a working well react implementation but while using the signIn function I can't have a custom function call to override what happens in the step 6 you highlighted above โฌ†๏ธ

Hey @swiiny I should have been more specific.

The workaround I had in mind was to put your identity provider behind a reverse proxy (nginx, apache, etc.). Then when the /token endpoint is called, append the client_secret to the request body.

I realize your original ask was for a way to override the call through this library's api, which I can't speak to.