-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: HttpClientHandler.Credentials should support Bearer auth type #91867
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationASP.NET Core has middleware to support JWT authentication, however, it expects the token to be supplied in a Meanwhile, NuGet provides a credential plugin system to allow NuGet to work with servers that expect HTTP basic, NTLM, Digest/Negotiate, and theoretically anything that HttpClient(Handler) supports. The way it does this is with a class that implements This is because System.Net's runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.cs Lines 124 to 128 in de0ab15
The requested change in HttpClient(Handler)'s behaviour will reduce the workload for server implementors, since clients would work "out of the box" and they won't need to handle JWT via Basic auth. And it will reduce workload on clients who need to authenticate to many different servers, by avoiding needing to special-case Bearer authentication when talking to servers who only allow JWT via Bearer auth. API ProposalNo changes to public API needed, but a new feature requests didn't seem appropriate for the bug report template, so I chose this template instead 🤷 API Usagestring jwt = GetJwtFromSomewhere();
var credentialCache = new CredentialCache
{
{ new Uri(prefix), "Bearer", new NetworkCredential(userName: null, password: jwt) }
};
using HttpClientHandler httpClientHandler = new()
{
PreAuthenticate = true,
Credentials = credentialCache
};
using HttpClient httpClient = new(httpClientHandler); Here's a full test program. Note that using System.Net;
HttpListener listener = new HttpListener();
string prefix = "http://localhost:1234/";
listener.Prefixes.Add(prefix);
listener.Start();
var authScheme = args.Length > 0 ? args[0] : "Bearer";
Task httpHandlerTask = HandleHttpRequests(listener);
var credentialCache = new CredentialCache
{
{ new Uri(prefix), authScheme, new NetworkCredential("username", "password") }
};
using HttpClientHandler httpClientHandler = new()
{
PreAuthenticate = true,
Credentials = credentialCache
};
using HttpClient httpClient = new(httpClientHandler);
using var response = await httpClient.GetAsync(prefix);
Console.WriteLine($"Client: response code {response.StatusCode}");
listener.Stop();
await httpHandlerTask;
async Task HandleHttpRequests(HttpListener listener)
{
while (true)
{
try
{
var request = await listener.GetContextAsync();
var authenticationHeader = request.Request.Headers.Get("Authorization");
if (string.IsNullOrEmpty(authenticationHeader))
{
Console.WriteLine("Server: unauthenticated request");
request.Response.AddHeader("WWW-Authenticate", authScheme);
request.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
Console.WriteLine("Server: authenticated request");
request.Response.StatusCode = (int)HttpStatusCode.OK;
}
request.Response.Close();
}
catch
{
// this is how http listener "gracefully" stops?
return;
}
}
} Alternative DesignsOne problem with using Otherwise, documentation explaining how to use bearer with NetworkCredential would be needed. I assume that username will be ignored, and only password would be copied into the header. RisksNo response
|
Triage: Looks like a useful thing we should consider. Adding to 9.0. |
Couple of random notes:
|
Regarding the first notes/questions, I'm not sure if you'd like me, as the request author, to respond. But generally, I'd have guessed that bearer would work more or less the same way that basic does. If I have creds in the CrenentialCache where I've listed the auth type as "BASIC", then I wouldn't expect that credential to be used unless the server responded with a I acknowledge that there are examples of web servers not sending the Anyway, I'm by no means an HTTP expert, definitely not a .NET HttpClient expert, I just happen to work on an app that uses it.
This would be problematic for NuGet, as we have our own class that implements The problem NuGet has with CredentialsCache is that CredentialsCache requires you to say what URL the credential is for, but NuGet creates a new HttpClient & HttpClientHandler for each package source, and then we want to use the same credential for all URLs used with that client. NuGet's "V3 protocol" uses JSON-LD, so the first json document contains URLs where we get other data from. Therefore, it's theoretically possible that the second URL we hit does not start with the prefix where we got the first. Needing to populate the So, it would be the least effort for NuGet to adopt if getting bearer tokens from |
Any response is welcome. The request for this API came up in different contexts multiple times over the years. Some of the contexts are HTTP specific, some are not. Any API change should preferably address this as widely as possible.
That would greatly reduce the utility of this added API. I guess it may be reasonable to send it only when That doesn't necessarily rule out adding an escape hatch to send We have the need for this since we communicate with wide variety of 3rd-party servers (including Microsoft ones). Not addressing this in some way would make this API significantly less usable for our purposes, unfortunately. I cannot comment on how the standards specifically define this behavior.
Glad to hear that, we are in the same boat for similar reasons :-)
That's where lies the problem. One way to solve this is to introduce |
If this feature is going to support |
Background and motivation
ASP.NET Core has middleware to support JWT authentication, however, it expects the token to be supplied in a
Authorization: Bearer <token>
header. By default it doesn't work withAuthorization: Basic <encoded username:password>
. Personally, I wouldn't be surprised if this is also true for other web development frameworks as well.Meanwhile, NuGet provides a credential plugin system to allow NuGet to work with servers that expect HTTP basic, NTLM, Digest/Negotiate, and theoretically anything that HttpClient(Handler) supports. The way it does this is with a class that implements
ICredentials
(the BCL'sCredentialCache
class is kind of similar, but also different, but for an example's purpose, it's close enough), and the NuGet calls out to credential providers to fill in the details as needed.This is because System.Net's
AuthenticationHelper
class hardcodes which auth schemes it will attempt to authenticate against:runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.cs
Lines 124 to 128 in de0ab15
The requested change in HttpClient(Handler)'s behaviour will reduce the workload for server implementors, since clients would work "out of the box" and they won't need to handle JWT via Basic auth. And it will reduce workload on clients who need to authenticate to many different servers, by avoiding needing to special-case Bearer authentication when talking to servers who only allow JWT via Bearer auth.
As a real use case, here's a request the NuGet client team received asking to handle Bearer, since we don't currently special-case Bearer, and therefore currently NuGet doesn't support bearer, since HttpCLient doesn't:
API Proposal
No changes to public API needed, but a new feature requests didn't seem appropriate for the bug report template, so I chose this template instead 🤷
API Usage
Here's a full test program. Note that
dotnet run
will show that only a single http request is attempted, which does not authenticate, where asdotnet run -- basic
will use HTTP basic authentication, and will authenticate on the second request:Alternative Designs
One problem with using
CredentialCache
to try to pass a bearer token (JWT, or otherwise), is thatCredentialCache.Add
takes in aNetworkCredential
, which hasUserName
andPassword
properties. But a bearer token is a single, opaque, string, which doesn't make sense to split up into separate UserName and Password components. If it's acceptable to add aCredentialCache.Add(Uri prefix, string authType, string value)
, whereAuthenticationHelper
will then use by creating the headerAuthorization: {authType} {value}
, that would make a lot more sense when trying to use bearer tokens.Otherwise, documentation explaining how to use bearer with NetworkCredential would be needed. I assume that username will be ignored, and only password would be copied into the header.
Risks
No response
The text was updated successfully, but these errors were encountered: