[SalesForce] Salesforce JWT User Hasn’t Approved This Consumer (Again)

I know others have asked this question.  The previous solutions have not worked.  Using the OAuth JWT flow.

  1.  All Users Can Authorize is selected
  2.  User authorized via https://login.salesforce.com/services/oauth2/authorize?client_id=&redirect_uri=&response_type=code
  3.  OAuth Scopes include
    Access and manage your data (api)
    Perform requests on your behalf at any time (refresh_token, offline_access)

POST to test.salesforce.com OR login.salesforce.com return
{"error":"invalid_grant","error_description":"user hasn't approved this consumer"}
Why am I receiving this error?

If I change the aud of my JWT from https://login.salesforce.com to https://XXX.force.com, I receive
{"error":"invalid_grant","error_description":"audience is invalid"}
which is the standard error response for an invalid token and is what I would expect.  I take that to mean my certificates are working correctly since I only gives the error when I change audience to an invalid value.

What am I missing? Why am I getting the user hasn't approved this consumer error?

Best Answer

From the Salesforce OAuth JWT Flow documentation (hidden):

A JWT OAuth 2.0 bearer assertion request looks at all the previous approvals for the user that include a refresh_token. If matching approvals are found, the values of the approved scopes are combined and an access_token is issued (with "token_type" value "Bearer"). If no previous approvals included a refresh_token, no approved scopes are available, and the request fails as unauthorized.

The previously attempted authorization URL used the web server flow. The server flow does NOT create a refresh token.
Incorrect: https://login.salesforce.com/services/oauth2/authorize?client_id=&redirect_uri=&response_type=code

I created a one-time curl request using the User Agent Flow:
Correct:
https://login.salesforce.com/services/oauth2/authorize?response_type=token&client_id=<your_client_id>&redirect_uri=<your_redirect_uri>

The User Agent Flow request set a refresh_token and everything worked smoothly from there, allowing me to use the JWT Flow for all future requests.