Skip to content

A client gets stuck forever on a failing authentication method #212

Closed

Description

@iskandergaba

Disclaimer: Content below is AI-generated under human supervision and scrutiny.

When a WinRMClient is given more than one authentication method, for example Kerberos then NTLM, or NTLM then Basic, it moves to the next method when one fails. But it never goes back to an earlier one. If the method it ended up on keeps failing, the client fails every request for the rest of its life, even after the original method would work again. Only creating a new client fixes it.

A single temporary failure is enough to get there, and that happens with valid credentials too:

  • A Windows host that is restarting, or that briefly can't reach its domain controller, rejects logins for a while;
  • A connection that drops in the middle of authentication gets rejected;
  • An unreachable Kerberos server (KDC) makes Kerberos fail.

The method the client gets stuck on can fail for good, for example:

  • NTLM, when the domain blocks it through policy;
  • Basic, when it's disabled on the host (the Windows default).

How to Reproduce (3.1.00, Windows Server 2025 with Basic Authentication Disabled)

The client uses the password array you pass in rather than a copy. So a temporary rejection can be simulated by starting with a wrong password and correcting the array afterwards:

char[] password = "Passw0rd!".toCharArray();
password[password.length - 1] = '?'; // wrong password, same length

try (WinRMClient client = WinRMClient.builder(host).http()
        .credentials("Administrator", password) // local account
        .authentication(AuthScheme.NTLM, AuthScheme.BASIC)
        .build()) {
    client.wql("SELECT Name FROM Win32_ComputerSystem").execute(); // rejected, as expected
    password[password.length - 1] = '!';                           // password is correct again
    client.wql("SELECT Name FROM Win32_ComputerSystem").execute(); // rejected, still uses Basic
    client.wql("SELECT Name FROM Win32_ComputerSystem").execute(); // rejected, still uses Basic and will keep doing so for good
}

After the first rejection, the client stays on Basic and never tries NTLM again. Since Basic is disabled, every subsequent request fails, while a new client with the same credentials connects immediately over NTLM.

The same happens with Kerberos then NTLM. After one Kerberos failure, for example with the KDC briefly unreachable, the client stays on NTLM, even through reconnections and after the KDC is back, which is a problem on hosts with NTLM disabled.

Possible Fix

Once every method has failed, start over from the first one on the next request. In FallbackAuthScheme.advance() (src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java):

    public boolean advance() {
        if (active != null && startIndex + 1 < candidates.size()) {
            active.reset();
            active = null;
            startIndex++;
            return true;
        }
+       active = null;
+       startIndex = 0;
        return false;
    }

The same rewind is needed in FallbackAuthScheme.authenticate(). When a method fails before the server is even contacted, for example Kerberos unable to get a ticket, the failure is raised there and advance() is never called:

        for (int i = startIndex; i < candidates.size(); i++) {
            ...
        }
+       startIndex = 0;
        throw new IllegalStateException(
            "All requested authentication schemes failed" +
                (lastFailure == null ? "" : " (last: " + lastFailure.getMessage() + ")"),
            lastFailure
        );

With both changes, a request on which every method failed makes the next request start over from the first method, whatever the reason each method failed.

EDIT: The suggested changes seemed to work for me. It will probably be fine fine, though you should check for possible regressions this might cause.

Activity

  1. self-assigned this
    on Oct 26, 2026
  2. added a commit that references this issue on Oct 26, 2026
    d96a13e
  3. iskandergaba commented on Oct 27, 2026

    @iskandergaba
    Author

    That was pretty fast! Thanks for your quick reaction @bertysentry!

    I do not mean to push or anything, but any idea on when will this fix be officially released?

  4. bertysentry commented on Oct 27, 2026

    @bertysentry
    Contributor

    @iskandergaba 3.1.01 release in progress! Should be available in Maven Central in 30 minutes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions