UPS API OAuth token request fails
Asked Answered
A

7

18

In the UPS developer portal, I have created an application that has a Client Id and a Client Secret. Next, I want to obtain an OAuth token so I can use it to access their other APIs. I am creating my token request as per the spec and I am receiving the following error:

{"response":{"errors":[{"code":"10400","message":"Invalid/Missing Authorization Header"}]}}

The spec has a "try it out" feature where you can obtain a test token. It prompts the user to fill in a x-merchant-id parameter and a grant_type form variable and creates a curl request that looks like this:

curl -X POST "https://wwwcie.ups.com/security/v1/oauth/token" 
     -H "accept: application/json" 
     -H "x-merchant-id: {My_Client_Id_Goes_Here}" 
     -H "Content-Type: application/x-www-form-urlencoded" 
     -d "grant_type=client_credentials"

For x-merchant_id, I have used my app’s Client Id. It is not clear if the value for grant_type should be the phrase client_credentials (the page makes it seem like this is the only valid value) or my app’s actual Client Secret. I have tried both and get the same error each time.

There are a million examples out there on how to use their (old style) API keys, but practically nothing about how to obtain an OAuth token except for the instructions linked above!

Attorneyatlaw answered 20/9, 2022 at 18:21 Comment(1)
Since this is the highest ranked result on Google: The python API example is also wrong. The URL contains '/security' twice. Took me a while to figure out.Wilheminawilhide
J
20

Your curl looks good to me, just missing the Authorization header which is a base64(id:secret)

curl -X POST "https://wwwcie.ups.com/security/v1/oauth/token" 
     -H "Authorization: Basic {id}:{secret}" 
     -H "accept: application/json" 
     -H "x-merchant-id: {My_Client_Id_Goes_Here}" 
     -H "Content-Type: application/x-www-form-urlencoded" 
     -d "grant_type=client_credentials"

If you're using the 'Try out' feature, select the Authorize button at the top and enter the client id and secret, that's where its used to set the Authorization header. One thing to note, the 'Try out' feature only work with the Test product(s) assigned to your app

Additional info

UPS have 2 environments

  • Testing: wwwcie.ups.com
  • Production: onlinetools.ups.com

Testing env only accepts Test Products, so note the product(s) that was added to your app

Jealousy answered 20/9, 2022 at 20:34 Comment(5)
Thank you for the help. Point taken on the test environment and being approved for the correct product ... that all looks good for me. Using the "Authorize" button on that page did indeed add an "Authorization: Basic blahblahblah" header to the request, but I still got a different error (code: 10401, message: ClientId is invalid). If I ignore this new error for a moment, what exactly would I put in the Authorization header when I am doing this from my app and not from the documentation page?Attorneyatlaw
Ha, should read your response more carefully! Their documentation page is leading people down the wrong path. It encodes the ups.com username/password combination and puts it in the header. It is actually supposed to be the client ID/secret combination that is supposed to be encoded. Once I did that, I got a token.Attorneyatlaw
5 upvotes (2023.08.29) on this means only 5 people have migrated from SOAP to REST? :) Thank you for posting this tip. Saved me huge headaches as documentation is still poorly formed on this in 2023.Freddie
@ConfueduProblemSolver you saved some headache. Also, token generation works without adding x-merchent-id header as this is basically same as client id.Decussate
thanks bro, u r a time saving; the line it matters: It encodes the ups.com username/password combination and puts it in the header. It is actually supposed to be the client ID/secret combination that is supposed to be encodedArpent
H
10

I was stuck with this issue for a long time.

Your comments did eventually help me. But I wanted to make it more clear for someone else reading this later....

Instead of using UPS username and password in the authorization header. You need to encode the clientId and secret with a colon between and send that.

For PHP:

$clientID = base64_encode("{clientID}:{clientSecret}");

$headers = array();
$headers[] = "Authorization: Basic $clientID";
$headers[] = 'Accept: application/json';
$headers[] = "X-Merchant-Id: {clientID}";
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
Heroism answered 22/10, 2022 at 23:13 Comment(0)
G
7

Just in case somebody with .NET/C# background will be looking for the similar topic - an UPS RESTFul API authorization and tracking info processing solution here is the one working well for me using proposed here approach:

#define TEST_MODE

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;

...

if (!ServicePointManager.Expect100Continue)
{
    ServicePointManager.Expect100Continue = true;
    ServicePointManager.SecurityProtocol = (SecurityProtocolType)(3072); // SecurityProtocolType.Tls;
    ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => true;
}

var myClientID = "{Type your ClientId here}";
var mySecretID = "{Type your SecretID here}";

#if TEST_MODE
var baseAddress = "https://wwwcie.ups.com";         // testing
#else
var baseAddress = "https://onlinetools.ups.com";  // production
#endif

var accessID = $"{myClientID}:{mySecretID}";
var base64AccessID = Convert.ToBase64String(Encoding.ASCII.GetBytes(accessID));

using (var client = new HttpClient())
{
    // Get Access Token
    var request = new HttpRequestMessage()
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri($"{baseAddress}/security/v1/oauth/token"),
        Content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "client_credentials")
        })
    };
    request.Headers.Add("Authorization", $"Basic {base64AccessID}");

    var response = await client.SendAsync(request);

    var jsonResult = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<JsonObject>(jsonResult);

    var access_token = result?["access_token"]?.ToString();

    // Get Tracking Info
    var trackingNumber = "1Z5338FF0107231059";  // provided by UPS for testing

    request = new HttpRequestMessage()
    {
        Method = HttpMethod.Get,
        RequestUri = new Uri($"{baseAddress}/api/track/v1/details/{trackingNumber}")
    };
    request.Headers.Add("Authorization", $"Bearer {access_token}");
    request.Headers.Add("transId", $"{DateTime.Now.Ticks}");
#if TEST_MODE
    request.Headers.Add("transactionSrc", $"testing");
#else
    request.Headers.Add("transactionSrc", $"{App Name and version}");
#endif

    response = await client.SendAsync(request);
    jsonResult = await response.Content.ReadAsStringAsync();

    Console.WriteLine(jsonResult);
}

[UPDATE]

As Nick recently noted in his comment to this solution "I had to add a TLS1.2 declaration at the top of the code, otherwise it was throwing an error about not being able to establish a secure connection/channel" the original code has to be corrected by adding the following code lines on top of it:

if (!ServicePointManager.Expect100Continue)
{
    ServicePointManager.Expect100Continue = true;
    ServicePointManager.SecurityProtocol = (SecurityProtocolType)(3072); // SecurityProtocolType.Tls;
    ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => true;
}

I'd also note that to have the solution code to compile under .NET Framework 4.7.x/4.8 the Package Manager's command

  Install-Package System.Text.Json

has to be executed in the Package Manager Console or you can instead of System.Text.Json use Newtonsoft.Json NuGet's package but in the latter case you may need to make some small code edits.

Glairy answered 9/11, 2022 at 9:23 Comment(3)
Thanks so much for this example. Pretty much used it as is. I had to add a TLS1.2 declaration at the top of the code, otherwise it was throwing an error about not being able to establish a secure connection/channel.Firebug
@nNck: Glad it helped you! Do you mean a code line as proposed here: "The request was aborted: Could not create SSL/TLS secure channel for HttpWebRequest" ? I should have it used somewhere in the other part of my application together with the two more code lines: ServicePointManager.Expect100Continue = true; and ServicePointManager.SecurityProtocol = (SecurityProtocolType)(3072); prepending the referred above solution.Glairy
yep - I had to add those two exact lines and everything functioned perfectly.Firebug
B
4

One more addition to the other answers: make sure you add the "OAuth" product to your UPS app. I had added "tracking" and "tracking test", but not OAuth. I was getting the "{"code":"10401","message":"ClientId is Invalid"}" response when I tried to get a token, even though I was sure I had everything else right.

Adding OAuth to my UPS app presumably added my ClientID to their OAuth system, and my token requests started working.

Bittencourt answered 27/10, 2022 at 16:16 Comment(0)
T
2

The following method uses C#, RestSharp, and System.Text.Json to request and receive the UPS Client OAuth Credentials

using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using RestSharp;
using RestSharp.Authenticators;
* * *
/// JSON Schema to receive the UPS OAuth Token
public class Ups_OAuthToken
{
    [JsonInclude]
    public string? token_type;

    [JsonInclude]
    public string? issued_at;

    [JsonInclude]
    public string? client_id;

    [JsonInclude]
    public string? access_token;

    [JsonInclude]
    public string? scope;

    [JsonInclude]
    public string? expires_in;

    [JsonInclude]
    public string? refresh_count;

    [JsonInclude]
    public string? status;
}

Ups_OAuthToken myUpsToken = null;
* * *
/// Setup a RestSharp RestClient and RestRequest
RestSharp.RestClient restClient = new("https://onlinetools.ups.com")
RestSharp.RestRequest apiRequest = new("/security/v1/oauth/token", Method.Post)
    {
        Authenticator = new HttpBasicAuthenticator("myclientid", "myclientsecret"))
    };    
apiRequest.AddHeader("Content-Type", "application/x-www-form-urlencoded");
apiRequest.AddHeader("x-merchant-id", "string");
apiRequest.AddParameter("grant_type", "client_credentials", true);
RestSharp.RestResponse apiResponse = restClient?.Execute(apiRequest)!

switch (apiResponse.StatusCode)
{
    case HttpStatusCode.OK:  // 200 Successful Operation
        // Authorization Token Received!
        myUpsToken = JsonSerializer.Deserialize<Ups_OAuthToken(apiResponse?.Content!.ToString()!, m_jsonOptions)!;
        break;
}
* * *

The code above is edited for concept illustration purposes. It was snipped from my application developed in Visual Studio 2022 v 17.6.5, C# 11.0, .NET 7.0, RestSharp 110.2.0 (via nuget).

Tipstaff answered 1/8, 2023 at 18:46 Comment(1)
Funny how I search for code like yours yesterday morning and you posted it literally minutes after that :-) Thank you thumbs upLille
C
1

Example for Linux/Ubuntu command line:

#!/bin/bash

ClientId=paste from your UPS Apps - Credentials - Client Id

ClientSecret=paste from your UPS Apps - Credentials - Client Secret


ClientIdAndSecret=${ClientId}:${ClientSecret}

EncodedClientIdAndSecret=$(echo -n "${ClientIdAndSecret}" | base64  -w0)

echo $EncodedClientIdAndSecret


curl -X POST "https://onlinetools.ups.com/security/v1/oauth/token"  \
     -H "Authorization: Basic ${EncodedClientIdAndSecret}"  \
     -H "accept: application/json"  \
     -H "x-merchant-id: $ClientID"  \
     -H "Content-Type: application/x-www-form-urlencoded"  \
     -d "grant_type=client_credentials"
Cyclopedia answered 4/4, 2023 at 22:34 Comment(0)
L
1

Example for Azure Logic Apps using HTTP action.

  1. Headers:
Content-Type: application/x-www-form-urlencoded

accept: application/json

x-merchant-id: your UPS Client ID
  1. Body
grant_type=client_credentials&code=YOUR_UPS_CLIENTID&redirect_uri=YOUR_CALLBACK_URL
  1. Authentication

Basic

Username is your UPS Client ID.

Password is your UPS Client Secret.


In my example I'm getting my Client ID and Client Secret values from Azure Key Vault. I'm also in a testing environment, so I'm using the UPS test server URI.

Azure Logic Apps HTTP Token Request Action

Lenssen answered 5/6, 2023 at 19:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.