Authorization API not working

I am developing an iOS app using Swift code and want to integrate the Plaid/Dwolla ACH processing. I have a common function that essentially creates the raw cURL commands for doing this. Of course for each endpoint the parameters of the cURL request change appropriately. This function works fine for all the Plaid endpoints (link tokens, access tokens, processor tokens, etc.). It also works with the Dwolla endpoints that I have tried with one big exception, Authorization.

If I click on the “Create Token” button when I log in to the dashboard, I get the temporary access token, which I can then use successfully for endpoints like “GET Root” (GET …api-sandbox.dwolla.com/), “POST Create Customer” (POST …api-sandbox.dwolla.com/ customers), and “GET Get Customers” (GET …api-sandbox.dwolla.com/ customers). These functions all worked fine using Authorization: Bearer access token.

However the Authorization function “POST https://api-sandbox.dwolla.com/token” did not work for me. I use the two parameters from my dashboard concatenated together for Authorization: Basic. The body was “grant_type=client_credentials”. All other API requests use a key:value pair for the body, so I’m not sure why you use a statement with an equal sign. So I also tried “grant_type”: “client_credentials” in the body. Both gave the same error:

[“code”: InvalidCredentials, “message”: Missing or invalid Authorization header.]

As I say above the Authorization: Bearer header works so I don’t know why the Authorization: Basic doesn’t work. I use the same two parameters as is used with the dashboard “Create Token” button.

Here is a printout of what I am sending (with secret edited). I had to remove https: from the links since it would not let me post with active links, but all the links are https://. The 33 bytes of the body translates to [“grant_type=client_credentials”].

▿://api-sandbox.dwolla.com/token
▿ url : Optional
▿ some ://api-sandbox.dwolla.com/token
- _url : ://api-sandbox.dwolla.com/token

  • cachePolicy : 0
  • timeoutInterval : 60.0
  • mainDocumentURL : nil
  • networkServiceType : __C.NSURLRequestNetworkServiceType
  • allowsCellularAccess : true
    ▿ httpMethod : Optional
    • some : “POST”
      ▿ allHTTPHeaderFields : Optional<Dictionary<String, String>>
      ▿ some : 2 elements
      ▿ 0 : 2 elements
      • key : “Authorization”
      • value : "Basic 37r8iVcRCxhOmo6YHRtfvPw5KD5hpXks5rRS5DZuEW2sx3QFDPnSbZKfmTzkAbjyTPP…”
        ▿ 1 : 2 elements
      • key : “Content-Type”
      • value : “application/x-www-form-urlencoded”
        ▿ httpBody : Optional
        ▿ some : 33 bytes
      • count : 33
        ▿ pointer : 0x00007fc34a89aa00
        • pointerValue : 140476745886208
          ▿ bytes : 33 elements
        • 0 : 91
        • 1 : 34
        • 2 : 103
        • 3 : 114
        • 4 : 97
        • 5 : 110
        • 6 : 116
        • 7 : 95
        • 8 : 116
        • 9 : 121
        • 10 : 112
        • 11 : 101
        • 12 : 61
        • 13 : 99
        • 14 : 108
        • 15 : 105
        • 16 : 101
        • 17 : 110
        • 18 : 116
        • 19 : 95
        • 20 : 99
        • 21 : 114
        • 22 : 101
        • 23 : 100
        • 24 : 101
        • 25 : 110
        • 26 : 116
        • 27 : 105
        • 28 : 97
        • 29 : 108
        • 30 : 115
        • 31 : 34
        • 32 : 93
  • httpBodyStream : nil
  • httpShouldHandleCookies : true
  • httpShouldUsePipelining : false

Here is the complete response from the endpoint:

▿ Optional

  • some : <NSHTTPURLResponse: 0x6000000724e0> { URL://api-sandbox.dwolla.com/token } { Status Code: 401, Headers {
    “Access-Control-Allow-Origin” = (
    “*”
    );
    “Content-Length” = (
    82
    );
    “Content-Type” = (
    “application/vnd.dwolla.v1.hal+json; profile=”://nocarrier.co.uk/profiles/vnd.error/""
    );
    Date = (
    “Sun, 27 Sep 2020 21:16:54 GMT”
    );
    Server = (
    cloudflare
    );
    “Set-Cookie” = (
    “__cf_bm=cce82e97ca661ec448cbebb3eed4ca4db9c5e875-1601241414-1800-Af5ZfJLsbPtR3Yfkw9IoG+6TWlaM5xs8u9rglrZ6X6FeQcN28YSGvLkbFtcJ9M7ZcDEKjxvhmWQ8CG7Tv8bKX6Y=; path=/; expires=Sun, 27-Sep-20 21:46:54 GMT; domain=.dwolla.com; HttpOnly; Secure; SameSite=None”
    );
    “cf-cache-status” = (
    DYNAMIC
    );
    “cf-ray” = (
    “5d983f981820f045-EWR”
    );
    “cf-request-id” = (
    05730613110000f045dc90d200000001
    );
    “expect-ct” = (
    “max-age=604800, report-uri=”://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct""
    );
    “x-request-id” = (
    “46508a67-ee16-4e2a-bfce-3c27dee3515e”
    );
    } }

Hi Burt – thanks for posting a detailed explanation and also the code for us to reference. This is very helpful! :slight_smile:

^ This may be where the issue lies.

The Authorization header expects a base64 encoded string of the client id:client secret. Would you be able to give that a try and see if it works? i.e. Concatenate client_id and client_secret separated with a :, and then base64 encode it.

Authorization: Basic Base64(client_id:client_secret)

Let me know if that works or not!

I was dumb ignoring that. The encoded string ends with an = sign which is what all the examples show. So I think I am sending the correct string now.
However, I just tried that but I get the same error.

Since now that I am fairly confident that I am sending the correct encoded string, the only thing left is the body of the request: grant_type=client_credentials. It seems like your /token endpoint does not like the way I am sending it. As an experiment I removed the body and I get the same error, so it does appear as though it is ignoring the body I send as:
[“grant_type=client_credentials”].

This brings up an interesting point. Why is this data in the body even required. It is the same for all users so it provides no additional information, it really makes no sense to have to send anything in the body. The base64 encoded string contains everything needed, why send anything in the body if everyone sends the same thing. Second point, if you are going to require this, why was it done as a phrase x=y, all other API requests use a [key:data] pair in the body as I have mentioned in previous posts. Who came up with this and why was it done in the non-standard way. How did it get past QA.

So I have a request. Can you modify the /token endpoint such that it simply ignores the body. Or if you really want to require data then in addition to the current phrase, add an option to accept it as:
[“grant_type”: “client_credentials”], the same as all your other API requests.

Can you talk to your engineering group about this.

Hmm…would you be able to give the following cURL command a try?

CLIENT_ID=CLIENTID
CLIENT_SECRET=CLIENTSECRET
AUTH=$(echo -ne "$CLIENT_ID:$CLIENT_SECRET" | base64)
echo $AUTH
curl \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --header "Authorization: Basic $AUTH" \
  --request POST \
  --data 'grant_type=client_credentials' \
  https://api.dwolla.com/token

Remember I am doing this from inside an iOS app, not from a terminal or browser. If you mean CLIENTID is my client_id and CLIENTSECRET is my client_secret, well that is what I am already doing. I concatenate them, separate with : and convert to base64, and I send this data after Basic. And I send the data in the body as previously mentioned.

So I’m not sure what you are asking. This is what I am doing. It simply does not seem to recognize the way I send the data in the body.

Ah gotcha. My bad. Perhaps this might help to try -

$id = "YOUR DWOLLA CLIENT ID HERE";
$secret = "YOUR DWOLLA CLIENT SECRET HERE"

$curl = curl_init();
//config request
curl_setopt($curl, CURLOPT_URL, DWOLLA_API_ENDPOINT."/token");
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS,
            "client_id=$id&client_secret=$secret&grant_type=client_credentials");
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

// receive server response ...
$server_output = curl_exec ($curl);
curl_close ($curl);

//pull access token

I don’t want to be difficult, but no, that does not help. What you are showing doesn’t even do the base64 encoding, or include the Authorization Basic header. I can see that the rest includes the information, but how can that possibly work.

If it helps I can post a code snippet of how I send the cURL request, but I don’t think it would prove much.

Again, I know you are trying to help, but can one of your developers get involved.
Thanks.

Hey @burte123,

A code snippet could help us track down what’s going on, but the key and secret are only intended to be used on a secure server and not distributed with client applications.

Do you have a server environment somewhere? If so, we have some SDKs that make it easier to get up and running depending on what language you’re using.

Thanks for responding. Please clarify the situation. I am using the Plaid+Dwolla integration embedded inside my iOS app. I store my client_id and client_secret as constants in the app. They are not stored in core data memory on the device. So they get compiled as part of the source code. Someone would have to reverse engineer the app and recreate the source code in order to see these constants. As far as I know no one has ever claimed to have been able to do this. So I think it is pretty secure. Are you saying Dwolla would not allow this and ultimately would not approve it for production?

In any case I also use Google Firestore in my app. That is certainly a secure server. I could store client_id and client_secret in my database and retrieve them whenever a user starts to initiate an ACH transfer. Of course, setting them as constants inside the app is much cleaner and simpler. So let me know what you think of all this.

As far as the actual problem here is a code snippet of how I compose and send the RAW protocol. I used the wrong terminology in previous posts as I am not really sending cURL commands, but simply sending RAW commands as shown in your API documentation. In full disclosure I did not write this function. I got it from Square. They publish it in their documentation for using Square with an iOS app. As I mentioned this function works for all Square, Plaid, and your (Dwolla) API endpoints, except Authorization.

    let url = URL(string: Utilities.Dwolla.SERVER_ROOT+"/token")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    let json = ["grant_type=client_credentials"]
    let httpBody = try? JSONSerialization.data(withJSONObject: json)
    request.addValue("Basic  MzdyOGlWY1JDeGhPbW82WUhSdGZ2UHc1S0Q1aHBYa3M1clJTNURadUVXMnN4M1FGRFA6blNiWktmbVR6a0FianlUUFBDdlFmZjVaSlc5TDJ1bHd…=", forHTTPHeaderField: "Authorization")
    request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    request.httpBody = httpBody
    
    URLSession.shared.dataTask(with: request) { data, response, error in}

It seems the issue is that your endpoint doesn’t like the way I send the body. I gave me feelings on this in previous posts so I won’t repeat them. What is your thinking on my requests. Or do you have code which I can use instead of the above which would work with this endpoint.
Thanks.

Yeah, unfortunately. We have security requirements around where API credentials are stored since they can be used to access bank accounts, PII, etc. of yours and your users.

For this reason, your API credentials need to be stored on a secure server and cannot be distributed with your app to end-user devices.


In this case we chose to implement OAuth 2, an industry standard auth framework. The grant_type=client_credentials request body is specified as part of the OAuth spec, which is why it’s different from the rest of our API requests.

Our official SDKs abstract this away so you don’t have to worry about it. If we don’t have an SDK for the language you’re using, I would look to see if an OAuth 2 library exists for that language (e.g. Ruby), or try Googling OAuth 2 client_credentials examples for you language.


The only thing that looks off to me is:

let httpBody = try? JSONSerialization.data(withJSONObject: json)

since the request body is expected to be application/x-www-form-urlencoded and not JSON encoded. It may work if you do:

let httpBody = "grant_type=client_credentials"

which is a application/x-www-form-urlencoded string.


I’m sure this isn’t what you wanted to hear regarding the distribution of credentials with your app not being allowed, but if you have any follow-up questions don’t hesitate to ask.

Success! I got it to work and return an access token. Your suggestion was on the right track but not quite. let httpBody = “x” doesn’t work because request.httpBody must be of type Data. It does not allow setting it to a String. And you can’t coerce the string to Data.

However, I did some searching as you suggested and found some source code in GitHub which allowed it to work. Here is the code (simplified from the original post).

    let httpBody = "grant_type=client_credentials".data(using: String.Encoding.utf8)
    request.httpBody = httpBody

To clarify the point about using a secure server for client_id and client_secret. I assume that it would be okay to use Google Firestore and I would store the base64(client_id:client_secret) in my database, not the individual elements.

I believe as long as:

  1. Credentials are stored securely (encrypted at rest)
  2. Credentials are not shared with end-user devices
    • Tokens created via the /tokens endpoint are only intended to be used by your server to interact with the Dwolla API because they grant permission to access the API on behalf of your entire account.

you should be good.


We do have some tokens such as funding sources tokens which can be created for a customer on your server and shared with a client (web browser, iOS app, etc.) in order to make requests directly to the Dwolla API from a device.

We’re looking to expand the number of endpoints that support this type of functionality, but as of now the majority of endpoints require requests be made from your server.