I am trying to use Dell's Warranty API. To do this you first need to get an access token (that expires after 1 hour) and then make API requests using that token. They have a tutorial for postman, which works fine, but I am trying to make something more automated since we have 1000 assets to lookup with this API.
I am trying to use java.net.http
even though numerous examples exist for the older APIs, I would prefer not to use external libraries or older APIs.
To get the token you send a request to their API with your client ID and client secret.
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
String tokenURL = "https://apigtwb2c.us.dell.com/auth/oauth/v2/token";
String clientID = "{redacted}";
String clientSecret = "{redacted}";
String formatted = clientID ":" clientSecret;
String encoded = Base64.getEncoder().encodeToString((formatted).getBytes());
HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).followRedirects(Redirect.NORMAL).connectTimeout(Duration.ofSeconds(10)).build();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(tokenURL)).headers("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8", "Accept", "application/json", "grant_type", "client_credentials", "Authorization", "Basic " encoded)
.POST(BodyPublishers.noBody()).build();
System.out.println(request.headers());
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
The server is responding with
400
{
"error":"invalid_request",
"error_description":"Missing or duplicate parameters"
}
According to their docs, 400 is a bad request. I am currently sending an empty body, but am not sure if that's what the issue is.
And I am not entirely sure what is missing. I have tried multiple combinations of headers without success. The same works just fine with cURL which is what led me to as far as I am now.
Here is the cURL request just in case..
curl -v https://apigtwb2c.us.dell.com/auth/oauth/v2/token -H "Accept: application/json" -u "{redacted}:{redacted}" -d "grant_type=client_credentials"
* Trying 143.166.28.87:443...
* Connected to apigtwb2c.us.dell.com (143.166.28.87) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: C=US; ST=Texas; L=Round Rock; O=Dell; CN=*.apis.dell.com
* start date: Jul 26 19:18:16 2021 GMT
* expire date: Jul 20 19:18:15 2022 GMT
* subjectAltName: host "apigtwb2c.us.dell.com" matched cert's "apigtwb2c.us.dell.com"
* issuer: C=US; O=Entrust, Inc.; OU=See www.entrust.net/legal-terms; OU=(c) 2012 Entrust, Inc. - for authorized use only; CN=Entrust Certification Authority - L1K
* SSL certificate verify ok.
* Server auth using Basic with user '{redacted}'
> POST /auth/oauth/v2/token HTTP/1.1
> Host: apigtwb2c.us.dell.com
> Authorization: Basic {redacted}
> User-Agent: curl/7.79.1
> Accept: application/json
> Content-Length: 29
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Pragma: no-cache
< Cache-Control: no-store
< X-Correlation-ID: {redacted}
< Content-Type: application/json;charset=UTF-8
< Content-Length: 127
< Date: Thu, 14 Oct 2021 18:56:28 GMT
< Server: dell
<
{
"access_token":"{redacted}",
"token_type":"Bearer",
"expires_in":3600,
"scope":"oob"
* Connection #0 to host apigtwb2c.us.dell.com left intact
Thanks!
CodePudding user response:
Body is required
According to your curl log, the request should have this content-type application/x-www-form-urlencoded
This kind of content-type needs a body, so that is your first error:
.POST(BodyPublishers.noBody())
Second error is that in oauth2 protocol, grant_type=client_credentials is not a header, it is a form parameter in the body. Also you curl snippet confirms that: curl ... -d "grant_type=client_credentials"
. Check this: curl -d
The Client Credentials grant
If Dell platform implements the oauth2 protocol strictly, they should implement the oauth2 spec:
https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
In which we can see that body just need:
- the grant_type in application/x-www-form-urlencoded style (not json)
- content-type header
- Authorization header which should be like your
String encoded = Base64 ...
If Dell implements another kind of this grant, you should read in the docs, something like this:
https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/
POST /token HTTP/1.1
Host: authorization-server.com
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
In which we can see that body needs the client_id and client_secret, unlike the spec in which credential are sent as basic auth header.
Java request with x-www-form-urlencoded body
If Dell implements this spec and assuming that you use Java 11, this untested code should work:
String url = "https://apigtwb2c.us.dell.com/auth/oauth/v2/token";
Map<String, String> parameters = new HashMap<>();
parameters.put("grant_type", "client_credentials");
parameters.put("client_id", "****");
parameters.put("client_secret", "****");
String form = parameters.keySet().stream()
.map(key -> key "=" URLEncoder.encode(parameters.get(key), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url))
.headers("Content-Type", "application/x-www-form-urlencoded")
.POST(BodyPublishers.ofString(form)).build();
HttpResponse<?> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode() response.body().toString());
Some providers, supports several types of request, so the previous snippet should work. If Dell implements strictly the main spec, just delete this part:
parameters.put("client_id", "****");
parameters.put("client_secret", "****");
and add your authorization header
String formatted = clientID ":" clientSecret;
String encoded = Base64.getEncoder().encodeToString((formatted).getBytes());
...
.headers("Content-Type", "application/x-www-form-urlencoded", "Authorization", "Basic " encoded)