Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Clarify default credentials usage for HttpClientHandler on UAP #21672

Merged
merged 1 commit into from Jun 29, 2017
Merged

Clarify default credentials usage for HttpClientHandler on UAP #21672

merged 1 commit into from Jun 29, 2017

Conversation

davidsh
Copy link
Contributor

@davidsh davidsh commented Jun 28, 2017

This PR clarifies how credential handling and specifically default
logged-on credentials actually works on the UAP platform.

First, "default credentials" are the current credentials of the
logged-on user, typically on an enterprise network joined to an
Active Directory domain. When these credentials are sent to a
proxy or server, only Negotiate, Kerberos, or NTLM schemes are
used. Those credentials can never be sent via other schemes
like basic or digest.

.NET Framework traditionally has two ways for developers to specify
using default logged-on credentials to send to the server. Either
set the .UseDefaultCredentials property to true or specify
CredentialCache.DefaultCredentials to the .Credentials property.

For proxy credentials, the developer could pass DefaultCredentials
via the custom IProxy object or via the .DefaultProxyCredentials
property.

.NET Framework would send default credentials anywhere (intranet
or internet) if specified by the developer.

The UAP platform implementation of System.Net.Http uses a different
set of rules for sending default credentials. It is based on whether
the app has Enterprise Authentication capability as well as if the
endpoint is specified in an intranet zone. This design is part of
the base native WinInet and WinHTTP stacks. It was a design choice
to provide optimum security as well as convenience for developers
writing apps for enterprise scenarios.

This PR does a few things. First, it keeps the .NET ICredentials
properties stored separately so that they correctly round-trip
on get and set. .NET NetworkCredential has 3 parameters (domain,
user, password). But WinRT PasswordCredential only has 2 parameters
(user, password). We now convert to the WinRT objects only when
we need to use them.

Second, this PR clarifies that the .UseDefaultCredentials property
is basically a no-op because the API models of default credentials
handling is different on the UAP platform as described above.

These platform differences will be documented here:
https://github.com/dotnet/corefx/wiki/ApiCompat

Fixes #19642
Fixes #21510

This PR clarifies how credential handling and specifically default
logged-on credentials actually works on the UAP platform.

First, "default credentials" are the current credentials of the
logged-on user, typically on an enterprise network joined to an
Active Directory domain. When these credentials are sent to a
proxy or server, only Negotiate, Kerberos, or NTLM schemes are
used. Those credentials can never be sent via other schemes
like basic or digest.

.NET Framework traditionally has two ways for developers to specify
using default logged-on credentials to send to the server. Either
set the .UseDefaultCredentials property to true or specify
CredentialCache.DefaultCredentials to the .Credentials property.

For proxy credentials, the developer could pass DefaultCredentials
via the custom IProxy object or via the .DefaultProxyCredentials
property.

.NET Framework would send default credentials anywhere (intranet
or internet) if specified by the developer.

The UAP platform implementation of System.Net.Http uses a different
set of rules for sending default credentials. It is based on whether
the app has Enterprise Authentication capability as well as if the
endpoint is specified in an intranet zone. This design is part of
the base native WinInet and WinHTTP stacks. It was a design choice
to provide optimum security as well as convenience for developers
writing apps for enterprise scenarios.

This PR does a few things. First, it keeps the .NET ICredentials
properties stored separately so that they correctly round-trip
on get and set. .NET NetworkCredential has 3 parameters (domain,
user, password). But WinRT PasswordCredential only has 2 parameters
(user, password). We now convert to the WinRT objects only when
we need to use them.

Second, this PR clarifies that the .UseDefaultCredentials property
is basically a no-op because the API models of default credentials
handling is different on the UAP platform as described above.

These platform differences will be documented here:
https://github.com/dotnet/corefx/wiki/ApiCompat

Fixes #19642
Fixes #21510
@davidsh
Copy link
Contributor Author

davidsh commented Jun 28, 2017

cc: @karelz

{
// The WinRT PasswordCredential object does not have a special credentials value for "default credentials".
// In general, the UWP HTTP platform automatically manages sending default credentials, if no explicit
// credential was specificed, based on if the app has EnterpriseAuthentication capability and if the endpoint

Choose a reason for hiding this comment

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

specificed [](start = 30, length = 10)

typo: specificed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will fix that typo in the next PR this week since I'm editing the same file.

@CIPop
Copy link
Member

CIPop commented Jun 29, 2017

These platform differences will be documented here:
https://github.com/dotnet/corefx/wiki/ApiCompat

Is there a doc bug tracking the changes?

@davidsh
Copy link
Contributor Author

davidsh commented Jun 29, 2017

Is there a doc bug tracking the changes?

There is no doc bug because I plan to update that document right after I merge the PR. That is what I did for the last few PRs. The document is a wiki and can be edited by anyone on GitHub.

@davidsh
Copy link
Contributor Author

davidsh commented Jun 29, 2017

@mmitche @stephentoub I noticed that the Windows runs have been running 5+ hours now. Is there something wrong with CI?

{
// We don't support changing the proxy settings in the NETNative version of HttpClient since it's layered on
// WinRT HttpClient. But we do support passing in explicit proxy credentials, if specified, which we can
// get from the specified or default proxy.
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this remove the " But we do support passing in explicit proxy credentials, if specified, which we get from the specified or default proxy" functionality?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't understand your comment. Are you saying I should put back the comment or revise it in some way?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I forgot to remove this from the review: I did see that the code handling explicit proxy credentials has been moved (not removed as I've originally thought).

@@ -167,99 +168,48 @@ public bool PreAuthenticate

public bool UseDefaultCredentials
{
get { return Credentials == null; }
get { return false; }
Copy link
Member

Choose a reason for hiding this comment

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

I thought that if the app has Enterprise Auth it will use default credentials and credentials haven't been set. Can we somehow implement capability detection and return the correct state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Enterprise Auth is not the only thing that determines if default credentials are used. It also depends on the endpoint itself and whether or not it is listed in the intranet zones.

}
else if (_rtFilter.ServerCredential == null)
{
// The only way to disable default credentials is to provide credentials.
Copy link
Member

Choose a reason for hiding this comment

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

Wasn't this comment true? If yes, it would allow apps to disable DefaultCredentials.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The comment was not true which is why it is being removed.

// empty values for username and password) indicates that there is no explicit credential. And that means
// that the default logged-on credentials might be sent to the endpoint.
//
// There is currently no WinRT API to turn off sending default credentials other than the capability
Copy link
Member

Choose a reason for hiding this comment

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

See other comment: isn't setting user/pass credentials going to disable default credentials?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, in general setting a non-null credential will always prevent default credentials from being sent since there is an explicit credential. However, having a "null" credential assigned to the properties doesn't mean "no credential". It simply means "no explicit credential" and the end result can be either no credential is sent, or a default logged-on credentials is sent.

Copy link
Member

Choose a reason for hiding this comment

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

I see: so basically we cannot implement DefaultCredentials=false because it needs to send at least some fake, non-null user-name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so basically we cannot implement DefaultCredentials=false because it needs to send at least some fake, non-null user-name.

Correct. And although I thought about doing that (creating a "fake" username, i.e. guid-string), it would actually break app-compat behavior with prior UWP .NET Core (1.0, 1.1, etc.). It is better to keep the behavior and document the platform differences.

// with empty strings for username and password inside the object. However, one can't assign
// empty strings to those properties; otherwise, it will throw an error.
RTPasswordCredential rtCreds = new RTPasswordCredential();
if (!string.IsNullOrEmpty(networkCred.UserName))
Copy link
Member

Choose a reason for hiding this comment

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

Instead of calling multiple COM pinvokes why not just decide first if the user/pwd are empty and then call the right RTPasswordCredential ctor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll look into doing that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See comment above.

NetworkCredential networkCred = (NetworkCredential)creds;

// Creating a new WinRT PasswordCredential object with the default constructor ends up
// with empty strings for username and password inside the object. However, one can't assign
Copy link
Member

Choose a reason for hiding this comment

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

According to docs this shouldn't be true: only the user must be not null: https://docs.microsoft.com/en-us/uwp/api/windows.security.credentials.passwordcredential

Copy link
Contributor Author

@davidsh davidsh Jun 29, 2017

Choose a reason for hiding this comment

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

So, I actually re-tested this. The documentation is wrong. That is why I coded it that way in the first place.

Using a null for either the username or password parameter in the constructor or setting the property gives:

"System.ArgumentNullException: Value cannot be null."

Using an empty string for either the username or password parameter in the constructor or setting the property gives:

"System.ArgumentException: The parameter is incorrect."

So, that is why I create the RTPasswordCredential with default constructor first. And then assign the properties only if non-null and non-empty.

Copy link
Member

Choose a reason for hiding this comment

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

You should open a bug or correct the docs then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened internal bug: 12569816.


private void SetFilterProxyCredential()
{
// We don't support changing the proxy settings in the NETNative version of HttpClient since it's layered on
Copy link
Member

Choose a reason for hiding this comment

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

Nit: it's the UAP version as well. NetNative shouldn't have any differences from UAP.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point. I can revise that comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I may revise that comment in the next PR depending on if I can get this PR merged since CI is acting weird.

@karelz karelz modified the milestones: UWP6.0, 2.1.0 Jun 29, 2017
Copy link
Member

@CIPop CIPop left a comment

Choose a reason for hiding this comment

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

There may be one potential perf improvement when initializing the PasswordCredential.

LGTM

@davidsh
Copy link
Contributor Author

davidsh commented Jun 29, 2017

@dotnet-bot Test Windows x64 Debug Build
@dotnet-bot Test Windows x86 Release Build

@davidsh davidsh merged commit d3f9f21 into dotnet:master Jun 29, 2017
@davidsh davidsh deleted the sdk_uwp branch June 29, 2017 02:13
@davidsh
Copy link
Contributor Author

davidsh commented Jun 29, 2017

@mmitche
Copy link
Member

mmitche commented Jun 29, 2017

@dotnet/dnceng PTAL. This might be related to the scale set issues from yesterday?

@sunandabalu
Copy link
Member

Scale sets have been updated and redeployed. Please retry failed/blocked build.

picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Clarify default credentials usage for HttpClientHandler on UAP

Commit migrated from dotnet/corefx@d3f9f21
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
8 participants