SalesforceCommerceCloud / pwa-kit

React-based JavaScript frontend framework to create a progressive web app (PWA) storefront for Salesforce B2C Commerce.
https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/pwa-kit-overview.html
BSD 3-Clause "New" or "Revised" License
283 stars 130 forks source link

[BUG] Not getting correct secret key in callback controller trigerred by SLAS #1878

Open TayyabSalmanMRM1996 opened 3 months ago

TayyabSalmanMRM1996 commented 3 months ago

While doing password-less login, as per the docs: https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-passwordless-login.html#prerequisites, we should send un-guessable secret in params. We're doing this:

await slasClient.authorizePasswordlessCustomer({ body: { userId: email, channel_id: site?.id, mode: 'callback', callback_uri: ${hostUrl}/s/US/passwordlesslogin/callback?secret=${SLAS_SECRET_PLACEHOLDER}, user_id: email, usid: cookie.get('usid') } })

We expect SLAS to decode SLAS_SECRET_PLACEHOLDER and change it to our correct secret key so that we can make it secure in the callback controller. But we don't see the correct secret key in the callback controller. We see _PLACEHOLDER_PROXY-PWA_KIT_SLAS_CLIENT_SECRET. Shouldn't it give the correct secret key? Else, we'll not be able to secure the callback controller.

We can't send the correct secret key in callback_uri because we don't want to expose it.

vcua-mobify commented 3 months ago

Hi @TayyabSalmanMRM1996, good questions!

As you've noticed, the purpose of /mobify/slas/private is to inject the client secret on requests that require a private client so that the secret is not exposed.

To limit when the client secret is injected so only the SLAS requests that need it get it, this endpoint by default is configured to only inject the actual client secret on requests sent to SLAS /oauth2/token endpoint.

As you are attempting to introduce new SLAS endpoints where the client secret injection will apply (under the hood, slasClient.getPasswordLessAccessToken will send a request to the SLAS /oauth2/passwordless/token endpoint) there is one more configuration we will need to tweak in the retail react app's ssr.js options object, in addition to useSLASPrivateClient.

The /mobify/slas/private endpoint uses a regex to check the request path before injecting the client secret to the request. This regex can be configured by defining a applySLASPrivateClientToEndpoints in your ssr.js

For example, to add /oauth2/passwordless/token, you'd have the following:

ssr.js

const options = {
    ...
    useSLASPrivateClient: true,
    applySLASPrivateClientToEndpoints: \/oauth2(\/passwordless)?\/token
TayyabSalmanMRM1996 commented 3 months ago

@vcua-mobify that code doesn't execute when I trigger the password-less login API.

const sendToken = useShopperLoginMutation('authorizePasswordlessCustomer') await sendToken.mutate({ body: { userId: email, channel_id: site?.id, mode: 'callback', callback_uri: ${hostUrl}/s/US/passwordlesslogin/callback?secret=${SLAS_SECRET_PLACEHOLDER}, user_id: email, usid: cookie.get('usid') }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })

I have following in ssr.js:

`useSLASPrivateClient: true,
applySLASPrivateClientToEndpoints: /oauth2\/(token|passwordless\/(login|token))/`
TayyabSalmanMRM1996 commented 3 months ago

Even after manually setting the proxy like slasClient.clientConfig.proxy = ${appOrigin}/mobify/slas/private, I see that this code chunk is executed and a secret was added in API call only.

But in the callback controller, there is no such secret that we can use to secure that callback controller.

TayyabSalmanMRM1996 commented 2 months ago

Hi @vcua-mobify - just taking a follow-up if there is any update on this and if we can do something from our side to get the correct secret in the request of the callback controller. Thanks!

vcua-mobify commented 2 months ago

Hi @TayyabSalmanMRM1996, I am not the most familiar with the intricacies of the passwordless login flow.

But if I am understanding your question correctly, you are asking how could your app make the request to exchange the /passwordless/token endpoint (so the request originates from the callback controller and is sent to SLAS), correct?

Or did you mean handling the secret when the callback controller receives the request from SLAS /passwordless/login endpoint (so the request originates from SLAS and is sent to your callback)?

If it is the former (your callback controller is sending a request to SLAS), assuming your controller also has access to the API, I think you could try sending a call via useShopperLoginMutation('getPasswordLessAccessToken') and that should send the call through the proxy.

TayyabSalmanMRM1996 commented 2 months ago

@vcua-mobify the call is sent through proxy. Since, we are triggering email in the callback controller, the issue is, anyone can use that callback controller ${hostUrl}/s/US/passwordlesslogin/callback?secret=${SLAS_SECRET_PLACEHOLDER}.

How can we make sure that requests are coming from SLAS? Or is there a way to validate that the authentication code received in this callback is a valid one? The request received in the callback controller that was triggered by SLAS should have the actual secret key for validation: https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-passwordless-login.html.

image