authts / react-oidc-context

Lightweight auth library based on oidc-client-ts for React single page applications (SPA). Support for hooks and higher-order components (HOC).
MIT License
657 stars 64 forks source link

Concerns Regarding `client_secret` Exposure in Client-Side Applications #1188

Open swiiny opened 5 months ago

swiiny commented 5 months ago

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?
pamapa commented 5 months ago

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....

swiiny commented 5 months ago

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:

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.

pamapa commented 5 months ago

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.

zach-betz-hln commented 5 months ago

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.

bitbythecron commented 5 months ago

@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!

pamapa commented 5 months ago

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.

bitbythecron commented 5 months ago

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?

pamapa commented 5 months ago

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

bitbythecron commented 5 months ago

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?

pamapa commented 5 months ago

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:

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

swiiny commented 5 months ago

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.

zach-betz-hln commented 5 months ago

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

zach-betz-hln commented 5 months ago

@swiiny in case it helps https://github.com/authts/react-oidc-context/issues/1208

swiiny commented 5 months ago

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

Thanks for #1208! 👀

zach-betz-hln commented 4 months ago

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:

swiiny commented 4 months ago

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 ⬆️

zach-betz-hln commented 4 months ago

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.