SalesforceCommerceCloud / sfcc-ci

Salesforce Commerce Cloud CLI
https://npmjs.com/package/sfcc-ci
BSD 3-Clause "New" or "Revised" License
231 stars 93 forks source link

`invalid_token`: Authorization errors with Sandbox API #88

Open johnboxall opened 4 years ago

johnboxall commented 4 years ago

Hey folks,

Thanks so much for this great tool!

I'm scripting management of our on-demand instances and having trouble using the access_token returned by sfcc-ci auth:client to do it.

As I understand it, to allow my script to manage our On Demand instances, I must:

  1. Create an API Key in Account Manager, setting appropriate scopes and noting its key and password as per this article
  2. Install this package
  3. Run something like:
    sfcc-ci auth:client "${AM_API_CLIENT_KEY}" "${AM_API_CLIENT_PASSWORD}"
    sfcc-ci sandboxes:list

So, following those steps, I first use my API Client to get an access_token:

sfcc-ci auth:client "${AM_API_CLIENT_KEY}" "${AM_API_CLIENT_PASSWORD}"

Which succeeds returning:

{
    "access_token": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "scope": "tenantFilter profile roles",
    "token_type": "Bearer",
    "expires_in": 1799 
}

Then I try to list the sandboxes:

sfcc-ci sandbox:list

Which fails with the following error:

{"statusCode": "401",
     "body":
      { "error": "invalid_token",
        "error_description": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }
}

🤔

I should note that if I try the same flow using auth:login rather than auth:client, things work:

sfcc-ci auth:login

# Browser opens and successfully auths, CLI acks auth.

sfcc-ci sandbox:list

# CLI successfully lists sandbox instances

This flow wouldn't work however as the intention is to just run a script, not have someone click buttons to auth things :)

Any ideas?

Thanks so much.

tobiaslohr commented 4 years ago

Hi @johnboxall, thanks for the kudos and the feedback!

There are two options on how to authenticate with the CLI, the interactive use (a human using the tool) or the automation use (for automated scripts or system to system integrations).

The command sfcc-ci auth:login "${AM_API_CLIENT_KEY}" runs an Oauth implicit flow (for interactive use) and the (Account Manager) user must authenticate him/herself via the Account Manager login (see sfcc-ci auth:login --help for details and examples).

The command sfcc-ci client:auth runs different Oauth flows for automation (see sfcc-ci client:auth --help for details and examples). In case you want to manage sandboxes for example, you still require an authenticated user (and thus provide Account Manager user credentials) to the command:

sfcc-ci client:auth "${AM_API_CLIENT_KEY}" "${AM_API_CLIENT_PASSWORD}" "${AM_USER}" "${AM_PWD}"

This runs an Oauth resource_owner_password_credentials grant.

sfcc-ci client:auth "${AM_API_CLIENT_KEY}" "${AM_API_CLIENT_PASSWORD}" without user credentials runs an Oauth client_credentials grant. This can be used for some, but not all (!) of the CLI commands. For example, you make use of the Data APIs on a B2C instance with only a client_credentials grant. However, for other CLI commands (like sandbox management, or Account Manager user and role management) you'd have to provide Account Manager user credentials. This is the also reason why you'e command sfcc-ci sandbox:list fails.

If you want to automate the sandbox management, it may be a good practice to use a dedicated Account Manager user with only the Sandbox API User role and then to disable 2FA for the Sandbox API User role in your org in Account Manager. This prevents you from being caught with 2FA for your automation user. Other (human) users typically have other roles in Account Manager, which you should have 2FA activated for, thus those user still have to do 2FA if they manage sandboxes via the CLI interactively.

Hope this helps and answers your question!

johnboxall commented 4 years ago

Thanks @tobiaslohr.

~Given your answer, my understanding is that it is not possible to interact with the Sandbox API using an access_token provided by sfcc-ci client:auth.~

My new steps are:

sfcc-ci client:auth "${AM_API_CLIENT_KEY}" "${AM_API_CLIENT_PASSWORD}" "${AM_USER}" "${AM_PWD}"
sfcc-ci sandbox:list

This worked successfully for me. Thank you!

A few notes:

Super meta feedback: I don't really understand why this API requires an Oauth grant (and more specifically why it would require the user credentials as opposed to the other grant). In future town, it would be nice if I could do what I need to with just an API Key + Secret.

bitmoji

betmakh commented 4 years ago

Hey guys,

I have a similar issue. So I authorize my account from CLI but when I try to access sandbox list I see 403 error. Do I need any more rights? I have access to the Control Center with these creds and able to restart sandboxes from there but not from CLI

sfcc-ci client:auth "${AM_API_CLIENT_KEY}" "${AM_API_CLIENT_PASSWORD}" "${AM_USER}" "${AM_PWD}"
Authentication succeeded
sfcc-ci sandbox:list
Error: Retrieving list of sandboxes failed: 403

With Debug mode on I see that response is:

{
    "kind": "Status",
    "code": 403,
    "status": "Failure",
    "error": {
        "status": "Failure",
        "message": "Access is denied",
        "reason": "AccessDenied"
    }
}
betmakh commented 4 years ago

Or does it work only with On-demand sandboxes?

tobiaslohr commented 4 years ago

Thanks @tobiaschudalla.

That's a different Tobias ;)

Given your answer, my understanding is that it is not possible to interact with the Sandbox API using an access_token provided by sfcc-ci client:auth.

Not, that is not true and the understanding is wrong. Of course the access_token obtained by the command sfcc-ci client:auth with API credentials and user credentials can be used to access resources on the Sandbox API server to manage sandboxes.

  • Currently, using the JavaScript API, it is not possible to implement the above flow. obtainToken is not exported from lib/auth and the exported methods do not accept both sets of credentials and a callback argument. To resolve this you could either export obtainToken or extend the signature of api.auth.

That is correct. The first and foremost usage for this tool is the CLI. The JavaScript API was never a first class citizen. If there are any JavaScript API enhancements, like the one mentioned, feel free to create an issue.

  • Using a user account means we'll need to regularly rotate credentials which is a pain ;P Super meta feedback: I don't really understand why this API requires an Oauth grant (and more specifically why it would require the user credentials as opposed to the other grant). In future town, it would be nice if I could do what I need to with just an API Key + Secret.

Yes, yes and yes. The CLI is only consuming the APIs being available, and implements the authentication flows (Oauth flows) required to do some. Some APIs require only an API client (like some of the OCAPI resources) other APIs (like Sandbox Management or Account Manager APIs for User management) require a user context hence user credentials. The user context is required right now, also for automation use case. For sure, the renewal of the user credentials is odd. The product team is working on enhancements to the APIs to streamline the API access and permissions. That may result in a complete drop of the user context in the future.

tobiaslohr commented 4 years ago

I have a similar issue. So I authorize my account from CLI but when I try to access sandbox list I see 403 error. Do I need any more rights? I have access to the Control Center with these creds and able to restart sandboxes from there but not from CLI

@betmakh It is a similar issue, but not the same. The reason why you are seeing this error, is that your Account Manager user does not have the Sandbox API User role, which is required for management of sandboxes. Please talk to the Account Administrator of the org you want to manage sandboxes for to request this role being granted to your user, execute the CLI commands again, and it will work.

johnboxall commented 4 years ago

For reference, here is the quick script I ended up with to explore the details of the Sandbox API:

https://gist.github.com/johnboxall/7fd92fb8667314ea4d1628fb5229f162

appanp-tjs commented 4 years ago

I am getting the following error, I do not think that there is anything wrong with the four input parameters to sfcc-ci client:auth command. How can I debug this issue? I have also tried with curl but same error.

{ error_description: 'Resource owner authentication failed', error: 'invalid_grant' }

appanp-tjs commented 4 years ago

Finally I could find the problem. Looks like you get this error when 2FA is enabled for Sandbox API automation user and there is no paired 2nd factor device, in the primary organisation of the automation user. In this case, the auth flow is redirected to the "Connect Salesforce Authentocator" page when I tried to login using the Swagger Sandbox API app. Once I disabled the "Sandbox API Role" from the ones which require 2FA (for the primary organisation of automation user), I could get the auth bearer token successfully.

tobiaslohr commented 4 years ago

@johnboxall can we close this ticket?

sepandar-sepehr commented 2 years ago

I'm seeing this error again today. Our primary org's 2FA got enabled automatically today by System Process and I cannot disable it!

ashwanik-github commented 2 years ago

https://gist.github.com/johnboxall/7fd92fb8667314ea4d1628fb5229f162

Yeah. We are also having a similar issue during fetching of access_token on the AM API - https://account.demandware.com/dw/oauth2/access_token Response: 401 Unauthorized { "error_description": "Client authentication failed", "error": "invalid_client" }

Since we have enabled the Salesforce Authenticator for 2FA/MFA the API calls (below using the curl) fail to fetch the token. curl -X POST "https://account.demandware.com/dw/oauth2/access_token" -H 'Content-Type: application/x-www-form-urlencoded' --data client_id=${SFCC_OAUTH_CLIENT_ID} --data client_secret=${SFCC_OAUTH_CLIENT_SECRET} --data grant_type=password --data username=${SFCC_OAUTH__USERNAME} --data password=${SFCC_OAUTH_USER_PASSWORD}|jq -r '.access_token' Nevertheless, the API call using sfcc-ci triggers MFA notifications but the curl does not. Any clue how we can fix this? Thanks!

tobiaslohr commented 2 years ago

invalid_client and invalid_token are different things, the way how the CLI obtains token through the resource owner password credentials grant is documented through the code as you probably know. There is nothing the CLI explicitly makes use of to trigger MFA. Usually this is specified by the MFA method the user / primary org of the user has set.

If the curl is correct or not, I cannot tell. However, this grant type is subject for deprecation in AM and hence we will remove this flow in the CLI. See also https://github.com/SalesforceCommerceCloud/sfcc-ci/wiki/Resource-Owner-Password-Credentials-Authentication-Failure.

As a consequence, I recommend to not use a service user for your use case anymore, but rather switch to a pure client_credentials grant.

esmiralha commented 1 year ago

I get the same error when running client:auth with just the api client id and api client secret.

DEBUG=* sfcc-ci client:auth '******' '******'
[DEBUG] Configuration loaded from /Users/****/dw.json
[DEBUG] Authorize via Oauth client_credentials grant
[DEBUG] Doing auth request, payload: {"grant_type":"client_credentials"}
{
  request: {
    debugId: 1,
    uri: 'https://account.demandware.com/dw/oauth2/access_token',
    method: 'POST',
    headers: {
      host: 'account.demandware.com',
      'content-type': 'application/x-www-form-urlencoded',
      authorization: 'Basic *********',
      accept: 'application/json',
      'content-length': 29
    },
    body: 'grant_type=client_credentials'
  }
}
{
  response: {
    debugId: 1,
    headers: {
      'cache-control': 'no-store',
      pragma: 'no-cache',
      'www-authenticate': 'Basic realm="/"',
      'content-type': 'application/json;charset=UTF-8',
      'content-length': '77',
      date: 'Wed, 26 Apr 2023 13:25:04 GMT',
      'referrer-policy': 'strict-origin-when-cross-origin',
      'strict-transport-security': 'max-age=15724800; includeSubDomains',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      vary: 'Accept-Encoding',
      'set-cookie': [Array],
      server: 'envoy',
      connection: 'close'
    },
    statusCode: 401,
    body: {
      error_description: 'Client authentication failed',
      error: 'invalid_client'
    }
  }
}
Error: Authentication failed: Client authentication failed
tobiaslohr commented 1 year ago

@esmiralha I think you've solved this already as part of https://sfcc-unofficial.slack.com/archives/CAUFG3SHF/p1682597925151959. The credentials passed in were wrong / got mangled (since not escaped properly), as a result the authentication failed.