24

I wrote the following HttpClient code, and it did not result in an Authorization header being sent to the server:

public static void main(String[] args) {
    var client = HttpClient.newBuilder()
            .authenticator(new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication("username", "password".toCharArray());
                }
            })
            .version(HttpClient.Version.HTTP_1_1)
            .build();
    var request = HttpRequest.newBuilder()
            .uri("https://service-that-needs-auth.example/")
            .build();
    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(System.out::println)
            .join();
}

I'm getting an HTTP 401 error from the service I'm calling. In my case, it's the Atlassian Jira Cloud API.

I have confirmed that my getPasswordAuthentication() method is not being invoked by HttpClient.

Why isn't it working, and what should I do instead?

0

1 Answer 1

53

The service I was calling (in this case, Atlassian's Jira Cloud API) supports both Basic and OAuth authentication. I was attempting to use HTTP Basic, but it sends back an auth challenge for OAuth.

As of the current JDK 11, HttpClient does not send Basic credentials until challenged for them with a WWW-Authenticate header from the server. Further, the only type of challenge it understands is for Basic authentication. The relevant JDK code is here (complete with TODO for supporting more than Basic auth) if you'd like to take a look.

In the meantime, my remedy has been to bypass HttpClient's authentication API and to create and send the Basic Authorization header myself:

public static void main(String[] args) {
    var client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .build();
    var request = HttpRequest.newBuilder()
            .uri(new URI("https://service-that-needs-auth.example/"))
            .header("Authorization", basicAuth("username", "password"))
            .build();
    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(System.out::println)
            .join();
}

private static String basicAuth(String username, String password) {
    return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
}
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for reporting this - it is now logged as bugs.openjdk.java.net/browse/JDK-8217237 - The work around you are suggesting looks like the right thing to do. Alternatively, if you don't supply any Authenticator, and wanted to check the server challenge first, you could choose to deal with the 401/407 reply instead of preemptively supplying the Authorization header.
Thanks for filing that issue. It will be an important improvement to the standards compliance of HttpClient. I just wanted to clarify, though: the issue here is not the same as the one you described in JDK-8217237. In the case of Atlassian Cloud, the only auth challenge sent by the server is for OAuth. There is no Basic auth challenge. Try curl -v https://example.atlassian.net/rest/agile/1.0/board/44/issue to see the response headers HttpClient would be dealing with in this case.
Oh. Well - if the server doesn't send the BASIC challenge there's not much that the HttpClient could do: the HttpClient only implements basic out of the box. The bug (JDK-8217237) is still a bug though.
pretty crazy! I can't believe that the fix version for this is 13 :| we hit this one today and I kept saying to the dev that it's obvious he is doing something wrong, how wrong was I...
I'm using JDK 21 and this still seems to be an issue.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.