Why does Stripe require OAuth tokens to pass through a third party server?

Can someone who understands OAuth better than me explain to me why Stripe REQUIRES that their OAuth Access Keys get shared with a third party?

I’ve tried RTFM, but my biggest hangup is that the OAuth docs appear describe a very different situation than mine. They usually describe a user agent (web browser) as the client. And they talk about “your users” as if I have a bunch of users that I’m going to be fetching access keys for.

Nah, this is server <–> server. I have a server. Stripe has a server. I am one user. All I need is ONE API key for ONE account. But I’m forced to use OAuth. It doesn’t seem appropriate, and it’s especially concerning that the “flow” requires the (non-expiring!) Access Token to be shared with a third party server. Why?!?

I recently learned that Stripe has been pushing OAuth (branded as “Stripe Connect”) to its integration apps as the “more secure” solution, compared to Restricted API Keys. In fact, we’ve found that most integrations we’ve encountered that use Stripe Connect are less secure than using Restricted API Keys because the (private!) tokens are shared with a third party!

I’ve been using Stripe to handle credit card payments on my e-commerce website for years. Recently, we updated our wordpress e-commerce website and all its plugins. And then we discovered that all credit card payments were broken because our Stripe Payment Gateway plugin stopped allowing use of Restricted API Keys. Instead they only support “Stripe Connect” (which, afaict, is a marketing term for OAuth). This change forced us to do a security audit to make sure that the new authentication method met our org’s security requirements. What we found was shocking.

So far we’ve started auditing two woocommerce plugins for Stripe, and both have admitted that the OAuth tokens are shared with their (the developer’s) servers!

One of them is a “Stripe Verified Partner”, and they told us that they’re contractually obligated by Stripe to use only “Stripe Connect” (OAuth) – they are not allowed to use good-'ol API Keys.

They also told us that Stripe REQUIRED them to include them in the OAuth flow, such that their servers are given our (very secret!) OAuth Access Keys!

The benefit of normal API Keys, of course, is that they’re more secure than this OAuth setup for (at least) two reasons:

  1. I generate the API keys myself, and I can restrict the scope of the keys permissions

  2. I store the key myself on my own server. It’s never transmitted-to nor stored-on any third party servers. Only my server and Stripe’s servers ever see it.

Can someone shine a light onto this darkpattern? I understand that standardization is good. OAuth Refresh Keys add security (this service doesn’t use them). But why-oh-why would you FORCE OAuth flows that share the (non-expiring) Access Tokens with a third party? And why would you claim that’s more secure than good-ol-API-keys?

Does OAuth somehow not support server<–>server flows? Or is it a library issue?

What am I missing?

  • meme_historian@lemmy.dbzer0.com
    link
    fedilink
    English
    arrow-up
    4
    ·
    1 day ago

    Having had a cursory look through the docs about Stripe Connect, it looks like you’re not doing business directly with stripe anymore(?).

    These plugins you’re using are acting as a “Platform” in the below diagram and make API requests on your behalf with their own Stripe Secret Key and your account id [0].

    Diagram of stripe connect. A platform sits in the middle and facilitates all stripe interactions for both sellers and customers

    So I guess the credentials you’re sharing is “just” the auth credential for the platform which will then turn around and will make API calls to stripe on your behalf.

    The way I see it you’d have to implement the API calls yourself and ditch the whole Stripe Connect/Platform model in order to be in charge. Although this is probably not worth it for you because you’ll probably drown in PCI-DSS requirements

    [0] https://docs.stripe.com/connect/authentication

    • maltfieldOP
      link
      fedilink
      English
      arrow-up
      3
      ·
      edit-2
      1 day ago

      Thanks. It’s a good guess, but that’s not the case.

      The developers confirmed that the only place the OAuth access tokens are stored is on my server. Of course, the dev’s server (which sees the [non-expiring!] access keys for >800,000 Stripe Accounts!) would be a ripe target for someone malicious. But it’s not designed to store the keys there. All subsequent connections to the Stripe API are done directly between my server and Stripe’s server (with no intermediary “platform”). The token is only exposed to the dev server when OAuth flow is first established. Then the dev server (effectively a MITM, by design) sends it down to my server for storing and future use.

      PCI compliance on my server isn’t an issue because all sensitive payment information is tokenized.

      The reason this is done is because Stripe doesn’t allow the redirect during the OAuth flow to be dynamic. It must be a predefined value that’s hard-coded into the app.

      For security purposes, Stripe redirects a user only to a predefined URI.

      That’s why Stripe forces you to expose your access tokens to the developer’s servers.

      I’d still appreciate if someone with more experience with OAuth than me knows if this is common. Seems like a very bad design decision to require users to transmit their bearer tokens through the developer’s servers.

        • maltfieldOP
          link
          fedilink
          English
          arrow-up
          3
          ·
          edit-2
          17 hours ago

          https://blog.doyensec.com/2025/01/30/oauth-common-vulnerabilities.html

          I haven’t seen it. Thanks for sharing!

          afaik, it doesn’t cover this use-case (where the Resource Server [Stripe] just uses the wrong flow – forcing us to expose our access keys to a third party).

          But, curious, it lists 0 attacks for the OAuth Flow that Stripe should be using here = Client Credentials Flow.

          Edit: ahhhhh, this paragraph is elucidating

          The Authorization Code Flow is one of the most widely used OAuth flows in web applications. Unlike the Implicit Flow, which requests the Access Token directly to the Authorization Server, the Authorization Code Flow introduces an intermediary step. In this process, the User Agent first retrieves an Authorization Code, which the application then exchanges, along with the Client Credentials, for an Access Token. This additional step ensures that only the Client Application has access to the Access Token, preventing the User Agent from ever seeing it.

          I confirmed that Stripe is using the Authorization Code Flow

          curl https://connect.stripe.com/oauth/token \
          -u sk_test_MgvkTWK1jRG3olSRx9B7Mmxo: \
          -d “code”=”ac_123456789” \
          -d “grant_type”=”authorization_code”
          

          …but it does appear to be using the wrong OAuth Flow type. They give the token to us in the end. There’s no need to expose it to a third party.

          So I guess “choosing the wrong flow type” would be a valid addition to the “attacks” section under Authorization Code Flow

            • maltfieldOP
              link
              fedilink
              English
              arrow-up
              1
              ·
              edit-2
              15 hours ago

              I will, but I’m 85% sure they already know – but they made a business decision to make one OAuth flow for all “platforms” for a consistent & simpler UX (at the expense of extra security risk, which they’ve accepted).

              Edit: wait, did you mean email stripe or email the pentest company that authored the article of common oauth vulns?

    • maltfieldOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      2 days ago

      Please read the question. I am not the developer; I can’t change to keycloak.

        • maltfieldOP
          link
          fedilink
          English
          arrow-up
          1
          ·
          edit-2
          2 days ago

          Can you elaborate? Any idea why Stripe would choose this design?

          I think Stripe generally has good security practices. But I just can’t understand this design choice. There has to be a reason…

          • Kaboom@reddthat.com
            link
            fedilink
            English
            arrow-up
            1
            ·
            1 day ago

            I honestly don’t know why. If you’re that curious, you could probably ask them directly.

            • maltfieldOP
              link
              fedilink
              English
              arrow-up
              2
              ·
              1 day ago

              I have been, but Stripe support hasn’t been helpful.

              Update: one of the plugin authors finally explained it well:

              It’s because Stripe doesn’t allow the redirect during the OAuth flow to be dynamic. It must be a predefined value that’s hard-coded into the app.

              For security purposes, Stripe redirects a user only to a predefined URI.

              That’s why Stripe forces you to expose your access tokens to the developer’s servers.

              I’d still appreciate if someone with more experience with OAuth than me knows if this is common. Seems like a very bad design decision to require users to transmit their bearer tokens through the developer’s servers.

              • Kaboom@reddthat.com
                link
                fedilink
                English
                arrow-up
                1
                ·
                1 day ago

                That sounds super sketchy to me. Are you forced to use stripe? If so, CYA. Put it in writing.

                • maltfieldOP
                  link
                  fedilink
                  English
                  arrow-up
                  2
                  ·
                  edit-2
                  17 hours ago

                  No, I’m not forced to use Stripe.

                  I’m looking at mollie now, but I don’t really know of any better alternatives to Stripe. Got a recommendation?

                  Anything that requires a PayPal account is not an option.

                  Edit: Mollie won’t let us create an account unless we push >50.000 EUR/mo. Yeah, we’re a small business. We’re wayyy under that limit. So no Mollie :(