-3

I recently noticed this warning while scanning the Stripe documentation:

You can use the client secret to complete the payment process with the amount specified on the PaymentIntent. Don’t log it, embed it in URLs, or expose it to anyone other than the customer. Make sure that you have TLS on any page that includes the client secret.

A web app I run has been appending the client secret along with the payment intent ID to the payment confirmation page (i.e. redirect URL) ever since I first integrated it, without any modification from me as far as I can remember, so I've always assumed the integration was designed to be this way until I read this warning.

Is it safe for the client secret to be in the redirect URL?

Hashim Aziz
  • 4,074
  • 5
  • 38
  • 68
  • 4
    "*Is it safe for the client secret to be in the redirect URL*" is clearly answered by the quote you've included, wherein it states "*Don’t [...] embed it in URLs*", no? – esqew Apr 14 '23 at 16:46
  • @esqew That depends on what's meant by "embed", it could also be read as "hardcoded", and the redirect URL could be read as an exception to most URLs because it's only exposed to the customer. As also mentioned, this behaviour is a result of the default integration path from the Stripe docs at the time without me modifying any part of that behaviour, so at the very least it's an ambiguous point that needs to be clarified, not downvoted. The warning is also new and I never noticed it at any time when initially integrating. – Hashim Aziz Apr 14 '23 at 16:51
  • 1
    It's common security knowledge that [URLs should never ever hold secrets](https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url). While I personally cannot independently verify your claim that "*the Stripe docs at the time*" did not make the claim you quote, the Internet Archive shows that the warning itself has been present on the page in question since *at least* [October 3rd, 2019](https://web.archive.org/web/20191003154940/https://stripe.com/docs/payments/payment-intents). – esqew Apr 14 '23 at 17:41
  • 2
    "the redirect URL could be read as an exception to most URLs" - on the client-side of things, no URLs are exempted – Chris Haas Apr 14 '23 at 17:46
  • Having read through everything posted by the OP as well as on Stripe's site, although I can't say anything is "safe", it is how Stripe works and I'm assuming (or hoping) that they have done due diligence to ensure "safety". – Chris Haas Apr 17 '23 at 14:48

1 Answers1

-2

This approach of Stripe integration in question still is the official way recommended by the main integration section of the Stripe documentation:

Make sure the return_url corresponds to a page on your website that provides the status of the payment. When Stripe redirects the customer to the return_url, we provide the following URL query parameters:

payment_intent: the unique identifier for the PaymentIntent
payment_intent_client_secret: the client secret of the PaymentIntent object

...

Use one of the query parameters to retrieve the PaymentIntent. Inspect the status of the PaymentIntent to decide what to show your customers. You can also append your own query parameters when providing the return_url, which persist through the redirect process.

This approach is then demonstrated with the following code:

// Initialize Stripe.js using your publishable key
const stripe = Stripe('pk_test_***');

// Retrieve the "payment_intent_client_secret" query parameter appended to
// your return_url by Stripe.js
const clientSecret = new URLSearchParams(window.location.search).get(
  'payment_intent_client_secret'
);

// Retrieve the PaymentIntent
stripe.retrievePaymentIntent(clientSecret).then(({paymentIntent}) => {
  const message = document.querySelector('#message')

  // Inspect the PaymentIntent `status` to indicate the status of the payment
  // to your customer.
  //
  // Some payment methods will [immediately succeed or fail][0] upon
  // confirmation, while others will first enter a `processing` state.
  //
  // [0]: https://stripe.com/docs/payments/payment-methods#payment-notification
  switch (paymentIntent.status) {
    case 'succeeded':
      message.innerText = 'Success! Payment received.';
      break;

    case 'processing':
      message.innerText = "Payment processing. We'll update you when payment is received.";
      break;

    case 'requires_payment_method':
      message.innerText = 'Payment failed. Please try another payment method.';
      // Redirect your user back to your payment page to attempt collecting
      // payment again
      break;

    default:
      message.innerText = 'Something went wrong.';
      break;
  }

This confirms my suspicion that the client_secret is, as the name suggests, is intended to be seen on the client-side (i.e. by the user who's just paid), and that the general language about embedding it in URLs does not apply to the return URL.

Samuel Liew
  • 76,741
  • 107
  • 159
  • 260
Hashim Aziz
  • 4,074
  • 5
  • 38
  • 68
  • Stripe says "*we provide the following URL query parameters*" - that's a bit different than what you mentioned "*a web app I run has been appending the client secret*'. In your case, who's appending them to return url? Your app or Stripe? – But those new buttons though.. Apr 15 '23 at 07:26
  • @EatenbyaGrue No, it's very much the same. "A web app I run has been appending the client secret" only says that the return URL has the client secret, not where it's coming from. It's deliberately ambiguous because at the time I wasn't sure. – Hashim Aziz Apr 15 '23 at 17:22
  • I guess that's a matter of semantics. Since you do not run Stripe's side, your statement makes it sound like your application, **the application that you are running**, is the one that appends those params. If this is not the case, I think your statement is misleading - not ambiguous. – But those new buttons though.. Apr 16 '23 at 00:57