Skip to content

HTTP/2: Add automatic downgrade to HTTP/1.1 for Windows authentication#123827

Open
Copilot wants to merge 22 commits intomainfrom
copilot/fix-http2-auth-downgrade
Open

HTTP/2: Add automatic downgrade to HTTP/1.1 for Windows authentication#123827
Copilot wants to merge 22 commits intomainfrom
copilot/fix-http2-auth-downgrade

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 31, 2026

Description

Session-based authentication (NTLM/Negotiate) requires persistent connections and fails over HTTP/2. When SocketsHttpHandler receives a 401 with these schemes on HTTP/2, it now automatically retries on HTTP/1.1 if the request is retryable. Additionally, the connection pool is marked so that future downgradeable requests go directly to HTTP/1.1 without an extra roundtrip.

Changes

Core retry mechanism:

  • Added RequestRetryType.RetryOnSessionAuthenticationChallenge to signal auth-driven downgrades
  • Http2Connection.SendAsync() detects session auth challenges (401 + NTLM/Negotiate) and checks all safety conditions before attempting retry
  • HttpConnectionPool.SendWithVersionDetectionAndRetryAsync() catches and retries on HTTP/1.1

Pool-level HTTP/2 disable:

  • When a session auth challenge is seen, the connection pool sets a flag (_http2SessionAuthSeen) so future downgradeable requests (RequestVersionOrLower) skip HTTP/2 entirely and go directly to HTTP/1.1
  • Uses Volatile.Read/Volatile.Write for thread-safe flag access
  • HTTP/2-only requests (RequestVersionExact) are unaffected and continue using HTTP/2
  • ALPN selection is explicitly controlled based on whether the connection is intended for HTTP/2

Safety constraints:

  • Only retries when ALL conditions are met:
    • Request has no content (request.Content == null)
    • Version policy allows downgrade (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrLower)
    • Auth is not disabled on the request (!request.IsAuthDisabled())
  • Returns original 401 response when any retry condition is not met (preserves backward compatibility)
  • No race conditions or unsafe content access

Test coverage:

  • Validates successful downgrade from HTTP/2 to HTTP/1.1 for GET requests without content
  • Validates subsequent requests also use HTTP/1.1 (pool flag works)
  • Validates 401 behavior when version policy prevents downgrade (RequestVersionExact)
  • Validates 401 behavior when request has content (POST requests)
  • Validates POST sets pool flag so subsequent GET uses HTTP/1.1
  • Validates HTTP/2-only requests still work after pool flag is set
  • Validates 401 behavior when auth is disabled after redirect (IsAuthDisabled)

Example

var handler = new SocketsHttpHandler { Credentials = credentials };
var client = new HttpClient(handler);

// Works: GET request without content + RequestVersionOrLower
var request = new HttpRequestMessage(HttpMethod.Get, "https://server/api");
request.Version = HttpVersion.Version20;
request.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;

// Previously: 401 Unauthorized (no retry)
// Now: Automatic retry on HTTP/1.1, successful auth
// Future downgradeable requests go directly to HTTP/1.1
var response = await client.SendAsync(request);

// Not retried: POST request with content
var postRequest = new HttpRequestMessage(HttpMethod.Post, "https://server/api");
postRequest.Content = new StringContent("data");
postRequest.Version = HttpVersion.Version20;
postRequest.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;

// Returns: 401 Unauthorized (cannot safely retry with content)
// But sets pool flag so subsequent downgradeable requests use HTTP/1.1
var postResponse = await client.SendAsync(postRequest);

// Not retried: GET request with RequestVersionExact policy
var exactRequest = new HttpRequestMessage(HttpMethod.Get, "https://server/api");
exactRequest.Version = HttpVersion.Version20;
exactRequest.VersionPolicy = HttpVersionPolicy.RequestVersionExact;

// Returns: 401 Unauthorized (version policy doesn't allow downgrade)
var exactResponse = await client.SendAsync(exactRequest);

Limitations

This implementation only automatically retries requests without content (e.g., GET, HEAD, DELETE without body) when the version policy allows downgrade and auth is enabled. POST and PUT requests with content will receive a 401 response, but the pool flag will be set so subsequent downgradeable requests go directly to HTTP/1.1. Requests with RequestVersionExact policy or disabled auth are unaffected.

Original prompt

This section details on the original issue you should resolve

<issue_title>HTTP2: SocketsHttpHandler doesn't downgrade to HTTP/1.1 during Windows authentication</issue_title>
<issue_description>When SocketsHttpHandler connects to a server using HTTP/2, it is unable to downgrade to HTTP/1.1 when the server requests Windows authentication (Negotiate or NTLM). The net result is that 401 is returned to the client without even trying to reconnect (with HTTP/1.1) and send credentials.

// Repro will be posted later
```</issue_description>

<agent_instructions>Add logic that allows the SocketsHttpHandler connection pool to attempt to retry requests when the HTTP/2 request failed due to a Windows auth challenge, and the user-specified version & policy allow for the upgrade to happen.
Keep in mind that not every request will be possible to retry (e.g. a POST where we've tried sending the body and are unable to rewind).</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
<comment_new><author>@karelz</author><body>
@wfurt can you please chat with @davidsh about design / ideas here?</body></comment_new>
<comment_new><author>@karelz</author><body>
Given we scoped HTTP/2 to gRPC scenarios, this can wait for post-3.0.</body></comment_new>
<comment_new><author>@Tratcher</author><body>
I noticed this while doing some auth testing. IE, Chrome, and Edge all have this fallback behavior. Also, the downgrade isn't just for the auth handshake, all subsequent requests are made on the HTTP/1.1 connection so they share the cached auth context.

WinHttpHandler is a bit different. The first HTTP/2 request challenges, falls back to HTTP/1.1, authenticates, and completes successfully. However, the next request is still made over HTTP/2, the client attempts the fallback again, and then it re-authenticates on the already authenticated connection. That could be a major performance hole to fall into.</body></comment_new>
<comment_new><author>@halter73</author><body>
This came up again. This time from a SignalR client user. See https://github.com/dotnet/aspnetcore/issues/45371. Prior to .NET 7, the SignalR client forced HTTP/1.1 but we started allowing HTTP/2 in .NET 7. We're considering backporting a fix for SignalR to force HTTP/1.1 if the customer sets `HttpConnectionOptions.UseDefaultCredentials` to true.

@karelz Even though we have a fix for SignalR, we can't help but think HttpClient should have avoided this for us. Do you think we could do something similar in .NET 8 at the SocketsHttpHandler level when UseDefaultCredentials is set? Of course, the handler could be smarter than SignalR and wait for a challenge before downgrading,</body></comment_new>
<comment_new><author>@karelz</author><body>
@wfurt @ManickaP @CarnaViire any thoughts on this? (marking for re-triage given new scenario being impacted)</body></comment_new>
<comment_new><author>@wfurt</author><body>
There are (at least) two distance parts IMHO. 
1) get parity with WinHttp - The existing retry logic will be problematic as this needs new connection (and possibly check for version policy). This can probably be done by refactoring the connection pool logic.
2) Browsers do have global cache and therefore they can remember state. `HttpClientHandler` is stateless and each request is processed independently. To fix that, we would need whole new concept in `SocketsHttpHandler`.  That may be useful for other reasons but it would be fundamental change IMHO.

While we _should_ do something I would keep it in `Future` for now given complexity and no up votes e.g. no real customer pain. The workaround is somewhat easy -> just set version to 1.1 for problematic sites. </body></comment_new>
<comment_new><author>@karelz</author><body>
Agreed with @wfurt. It seems it is still rather rare with a complex solution, so leaving it in Future sounds like the right option now.
@halter73 let us know if you disagree.</body></comment_new>
</comments>

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix SocketsHttpHandler downgrading to HTTP/1.1 for Windows authentication HTTP/2: Add automatic downgrade to HTTP/1.1 for Windows authentication Jan 31, 2026
Copilot AI requested a review from MihaZupan January 31, 2026 01:21
Copy link
Copy Markdown
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MihaZupan
Copy link
Copy Markdown
Member

The PR change as-is would only handle requests without a body, and automatically retry on 1.1.
This has two problems:

  • We'll do an additional roundtrip for every successful request
  • As soon as you try sending a POST request, you'll be in the same situation as before

Alternatively, we could disable HTTP/2 on the connection pool if we reach this case, so future requests would just start with 1.1. This also has problems:

  • If you're talking to an API that only needs session auth in an edge case, you might not love the overhead of ALL requests now avoiding HTTP/2. Maybe that's niche enough to ignore.
  • If your first request is POST, it'll still fail since we don't know whether we can retry ourselves.
    • Doesn't make it worse than it already is today. At least if you retry yourself it would start working after the first request.
    • We should be able to fix this in the future by adding some sort of TryReset API on the HttpContent (which I was thinking about for other scenarios already)

I'm leaning towards trying out the alternative approach.

MihaZupan and others added 7 commits March 17, 2026 16:52
When an HTTP/2 request receives a session-based auth challenge (NTLM/Negotiate),
disable HTTP/2 on the connection pool so all future requests use HTTP/1.1 directly.
This avoids extra roundtrips for subsequent requests and ensures POST requests work
after the initial downgrade.

- Add DisableHttp2() method to HttpConnectionPool
- Catch RetryOnSessionAuthenticationChallenge in SendWithVersionDetectionAndRetryAsync
  and call DisableHttp2() before retrying
- Rewrite tests using HttpAgnosticLoopbackServer for proper HTTP/2→1.1 testing
- Add tests for: pool downgrade, subsequent requests, exact version policy,
  content requests, and non-auth HTTP/2 responses

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of disabling HTTP/2 entirely (which would break HTTP/2-only requests),
add a _http2SessionAuthSeen flag that only affects requests with
RequestVersionOrLower policy. HTTP/2-only requests (RequestVersionExact,
RequestVersionOrHigher) continue to work on HTTP/2 as before.

Also update GetSslOptionsForRequest to avoid negotiating h2 ALPN for
downgradeable requests after session auth has been seen.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Make _http2SessionAuthSeen volatile for cross-thread visibility
- Always set the pool flag when a session auth challenge is detected,
  even for requests that can't be retried (e.g. POST with content).
  This ensures subsequent downgradeable requests benefit immediately.
- Move flag-setting from catch handler to Http2Connection.SendAsync
- Add test: POST triggers flag, subsequent GET uses HTTP/1.1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of inspecting the request to decide ALPN, pass the intended
protocol version into ConnectAsync. This avoids the bug where a queued
HTTP/2 connection establishment could pick up a downgradeable request
and negotiate HTTP/1.1 ALPN, which would trigger HandleHttp11Downgrade
and disable HTTP/2 for the entire pool.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MihaZupan MihaZupan force-pushed the copilot/fix-http2-auth-downgrade branch from f355c6e to bcd9d4b Compare March 18, 2026 17:17
@MihaZupan MihaZupan added this to the 11.0.0 milestone Mar 18, 2026
@MihaZupan MihaZupan marked this pull request as ready for review March 19, 2026 14:10
Copilot AI review requested due to automatic review settings March 19, 2026 14:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates SocketsHttpHandler to automatically fall back from HTTP/2 to HTTP/1.1 when an HTTP/2 response indicates a session-based Windows authentication challenge (NTLM/Negotiate), enabling successful authentication for retryable requests while preserving behavior when downgrade is not allowed.

Changes:

  • Added a new retry signal (RequestRetryType.RetryOnSessionAuthenticationChallenge) to represent auth-driven protocol fallback.
  • Detected NTLM/Negotiate challenges in Http2Connection.SendAsync() and triggered an HTTP/1.1 retry (when safe), while also marking the pool to prefer HTTP/1.1 for future downgradeable requests.
  • Updated connection establishment to explicitly control ALPN selection based on whether the connection is intended for HTTP/2, and added functional tests for downgrade behavior and constraints.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs Adds functional tests verifying HTTP/2-to-HTTP/1.1 downgrade behavior and non-downgrade constraints.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs Detects session-auth challenges on HTTP/2 and signals retry/pool behavior updates.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.cs Skips HTTP/2 after session-auth is seen (for downgradeable requests) and handles the new retry type by retrying on HTTP/1.1.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.Http2.cs Adds pool flag tracking for session-auth challenges.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.Http1.cs Updates connection creation to pass the new “isForHttp2” signal into shared connect logic.
src/libraries/System.Net.Http/src/System/Net/Http/RequestRetryType.cs Adds a new retry enum value for session-auth downgrade retries.

You can also share your feedback on Copilot code review. Take the survey.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Note

This review was generated by Copilot.

🤖 Copilot Code Review — PR #123827

Holistic Assessment

Motivation: The PR addresses a real problem — NTLM/Negotiate authentication schemes require a persistent, connection-bound handshake that doesn't work over HTTP/2 (which multiplexes streams). When an HTTP/2 response contains a session-based auth challenge, the client needs to retry on HTTP/1.1. This is a genuine gap in the current implementation.

Approach: The PR uses the existing RequestRetryType-based exception/retry pattern (consistent with RetryOnConnectionFailure and RetryOnLowerHttpVersion) to downgrade to HTTP/1.1 on auth challenges. A pool-level flag (_http2SessionAuthSeen) causes future downgradeable requests to skip HTTP/2, while HTTP/2-only requests continue to work. The ALPN negotiation is also correctly adjusted via the new isForHttp2 parameter to prevent TLS from selecting h2 when the intent is HTTP/1.1.

Summary: ⚠️ Needs Human Review. The code is well-structured, follows existing patterns, and has thorough test coverage. However, there are design-scope questions about the pool-wide flag granularity and a minor thread-safety concern that warrant maintainer judgment. (Multi-model review: findings validated by GPT-5.2; Claude Sonnet 4.6 agent timed out.)


Detailed Findings

✅ Retry Pattern — Correct and consistent

The exception-based retry flow follows the existing pattern precisely:

  1. Http2Connection.SendAsync detects the auth challenge and throws with RetryOnSessionAuthenticationChallenge
  2. SendWithVersionDetectionAndRetryAsync catches it, sets request.Version = HttpVersion.Version11, and loops
  3. The exception is fully handled within the pool and never escapes to SendAsyncMultiProxy

The guard conditions are sound: only retry when Content is null (can't rewind streams), VersionPolicy == RequestVersionOrLower (downgrade allowed), and !IsAuthDisabled(). The response is properly disposed before throwing. (Flagged by both models as correct.)

✅ ALPN Negotiation Fix — Important and correct

Threading isForHttp2 into ConnectAsyncGetSslOptionsForRequest ensures that when a downgraded request creates an HTTP/1.1 connection, the TLS ALPN negotiation doesn't advertise h2. Without this, the server could select HTTP/2 via ALPN, defeating the downgrade. This is a critical detail handled correctly.

✅ Test Coverage — Thorough

Six test methods cover the key scenarios:

  • Basic downgrade (HTTP/2 → HTTP/1.1) for both NTLM and Negotiate
  • Pool flag persistence across subsequent requests
  • RequestVersionExact returns 401 as-is (no retry)
  • POST with content returns 401 (can't safely retry)
  • POST sets pool flag even though it can't retry itself
  • HTTP/2-only requests continue working after the flag is set

This covers the major code paths and edge cases well.

⚠️ Pool-wide Downgrade Granularity — Design question for maintainer

_http2SessionAuthSeen is set for any 401 response containing NTLM/Negotiate headers, regardless of the specific path or whether the client intends to authenticate. Once set, all RequestVersionOrLower requests to that origin are routed to HTTP/1.1 for the lifetime of the pool.

This could be overly broad for mixed workloads where only certain endpoints require session auth while others benefit from HTTP/2. For example, a single protected endpoint triggering a 401 would downgrade all anonymous traffic to the same origin.

That said, this mirrors the existing HandleHttp11Downgrade pattern (which sets _http2Enabled = false globally when ALPN fails), and the flag only affects RequestVersionOrLower requests — HTTP/2-only requests still work. The simplicity is a reasonable trade-off, but the maintainer should confirm this pool-wide scope is intentional.

⚠️ Thread Safety of _http2SessionAuthSeen — Benign but worth noting

The field is a plain bool without Volatile.Read/Write. While bool reads/writes are atomic and the flag only transitions false → true (no risk of tearing), there's no memory barrier guaranteeing prompt visibility to concurrent readers. A concurrent request might still attempt HTTP/2, receive another auth challenge, and retry — which is functionally correct but wasteful.

This is consistent with how _http2Enabled is handled elsewhere in the codebase (also a plain bool without volatile), so it follows the established pattern. The commit history shows volatile was explicitly dropped per earlier code review feedback (commit 5728403). Flagging for awareness but not a blocker.

💡 Missing IsAuthDisabled Test Case

There's no test covering the scenario where request.IsAuthDisabled() is true. In that case, the pool flag should still be set (it is), but the request should not retry (it won't). A test for this would document the intended behavior, though the production code handles it correctly.

💡 RequestVersionOrHigher Interaction

A request with Version = 1.1 and VersionPolicy = RequestVersionOrHigher can be routed to HTTP/2 and receive a session auth challenge. In this case, the response is returned as-is (no retry, since the policy isn't RequestVersionOrLower), and the pool flag is set. This seems correct — such a request explicitly opted into version negotiation — but there's no test for this scenario.

Generated by Code Review for issue #123827 ·

@MihaZupan
Copy link
Copy Markdown
Member

@copilot Add the "💡 Missing IsAuthDisabled Test Case" suggested by @copilot above

When auth is disabled on a request (e.g., after a redirect with
NetworkCredential), the handler should not retry/downgrade even
if other conditions are met. This test verifies the 401 is returned.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/7f52da7d-bfc5-4f5d-846b-836ce47c5cc5

Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 7, 2026

@copilot Add the "💡 Missing IsAuthDisabled Test Case" suggested by @copilot above

Added in 681fdea. The test verifies that when auth is disabled after a redirect (using NetworkCredential, not CredentialCache), the handler returns the 401 response without attempting the HTTP/2→HTTP/1.1 downgrade, even though all other retry conditions are met.

Copilot AI review requested due to automatic review settings April 10, 2026 14:11
// We can only safely retry if there's no request content, as we cannot guarantee
// that we can rewind arbitrary content streams.
// Additionally, we only retry if the version policy allows downgrade.
if (request.Content is null &&
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we retry any content-less request? What about non-idempotent POST without content?
And does it matter if we've received an error anyway?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think since it is 401 AuthChallenge that it guarantee server has not yet interpret the request, so retrying requests which was not yet accepted is most probably OK. There could be rare cases like rate limitations which could now count one request as two, but line 2075 somehow deals with it.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an automatic HTTP/2 → HTTP/1.1 fallback path in SocketsHttpHandler when an HTTP/2 response indicates a session-based Windows auth challenge (NTLM/Negotiate), and adds functional tests validating downgrade behavior and pool-level avoidance of HTTP/2 for future downgradeable requests.

Changes:

  • Introduces a new retry signal (RequestRetryType.RetryOnSessionAuthenticationChallenge) and throws it from Http2Connection when a session-auth 401 is detected and the request is safe to retry.
  • Updates HttpConnectionPool.SendWithVersionDetectionAndRetryAsync to catch that retry type, retry on HTTP/1.1, and to skip HTTP/2 for RequestVersionOrLower requests after a challenge has been seen.
  • Adds functional tests covering downgrade, persistence of the pool flag, and non-downgrade cases (exact version policy, request content, auth-disabled-after-redirect).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs Adds new loopback-based tests asserting HTTP/2 session-auth challenges trigger HTTP/1.1 behavior and that pool behavior persists.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs Detects NTLM/Negotiate 401 challenges on HTTP/2, sets the pool flag, and triggers an HTTP/1.1 retry when safe.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.Http2.cs Introduces a pool flag indicating session-auth was seen and provides a setter method.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.Http1.cs Threads an isForHttp2 parameter through connection establishment to control TLS/ALPN selection.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.cs Skips HTTP/2 after session-auth is seen for downgradeable requests; retries on HTTP/1.1 for the new retry type; adds isForHttp2 to connection establishment to control TLS/ALPN options.
src/libraries/System.Net.Http/src/System/Net/Http/RequestRetryType.cs Adds RetryOnSessionAuthenticationChallenge.

Comment on lines +171 to +173
// When an HTTP/2 request receives a session-based auth challenge (NTLM/Negotiate),
// the pool should disable HTTP/2 and retry the request on HTTP/1.1.
await HttpAgnosticLoopbackServer.CreateClientAndServerAsync(
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test comment says the pool "disable[s] HTTP/2", but the implementation only skips HTTP/2 for downgradeable requests (and still allows HTTP/2 for RequestVersionExact). Consider rewording the comment to avoid implying HTTP/2 is globally disabled for the pool.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@rokonec rokonec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚢

(request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) &&
(request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure)) // prefer HTTP/1.1 if connection is not secured and downgrade is possible
(request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure) && // prefer HTTP/1.1 if connection is not secured and downgrade is possible
!(_http2SessionAuthSeen && request.VersionPolicy == HttpVersionPolicy.RequestVersionOrLower)) // skip HTTP/2 for downgradeable requests after session auth
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:
When request has Version=1.1 with RequestVersionOrHigher on HTTPS, it can get upgraded to HTTP/2 (because "or higher" allows that). But after pool flag is set, the new check only blocks RequestVersionOrLower requests from going to HTTP/2 - it doesn't block RequestVersionOrHigher ones.

So this request still goes to HTTP/2, gets 401 with NTLM/Negotiate, and then retry logic also only kicks in for RequestVersionOrLower so retry doesn't happen and raw 401 is returned. To fix that the retry logic shloud allow RequestVersionOrLower && request.Version < 2

Probably fine to not handle this for now -> the combination of Version=1.1 + RequestVersionOrHigher + Windows auth + HTTP/2 server is pretty unusual, and behavior is same as what happens today without the fix. Could be addressed later if someone actually hits it.

// We can only safely retry if there's no request content, as we cannot guarantee
// that we can rewind arbitrary content streams.
// Additionally, we only retry if the version policy allows downgrade.
if (request.Content is null &&
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think since it is 401 AuthChallenge that it guarantee server has not yet interpret the request, so retrying requests which was not yet accepted is most probably OK. There could be rare cases like rate limitations which could now count one request as two, but line 2075 somehow deals with it.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP2: SocketsHttpHandler doesn't downgrade to HTTP/1.1 during Windows authentication

7 participants