Skip to content
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

Windows Authentication: Different Groups returned for Auto-Login or Explicit Manual Login #14766

Closed
RickStrahl opened this issue Oct 6, 2019 · 8 comments
Labels

Comments

@RickStrahl
Copy link

@RickStrahl RickStrahl commented Oct 6, 2019

I have an ASP.NET Core 3.0 application that works with local Intranet Windows Authentication to identify logged in users. Using the standard Windows Authentication behaviors I'm able to capture the user's WindowsIdentity without an issue.

However, depending on how the user is logged into the browser using either automatic Intranet Browser login (ie. no password dialog) or explicitly logging in using the browser Password dialog box, I get different results for the user's groups.

The following is an API request that echos back user information including a filtered group membership list (that excludes built-in accounts). The one on the left is a manual login, the one on the right an auto-login.

For the explicit login I correctly see all the custom groups the user is part of. However, for the auto-login, those same groups do not show up:

image

I also took a close look at the User and Identity instances on the server, and it's referencing the exact same SIDs for the user, so it seems strange that different results are being returned for the Group Membership.

Any ideas why the group list is different when I am getting the same account returned? Note the groups are local so it shouldn't be an issue due to domain access.

Note: I'm testing locally on localhost even, and to test this I set the Windows Proxy Settings here:

image

With the checkboxes off I'm forced to login. With them on (in Chromium browsers anyway) I have to explicitly enter my credentials into the browser's login dialog.

Environment

  • Windows 10
  • Local Logons
  • Using Chrome/Edgium (they auto-logon by default)
  • Using Kestrel or IIS Express (tried both same behavior)

Repro Steps

  • Create a Web Application with Windows Auth enabled.
  • Add an endpoint or middleware Use handler after authentication
  • Make sure you have or add some user defined roles (Local is what I'm using here)
  • Access via Edge Chrome on local machine
    Expected: Auto-login and you won't be able to see the custom groups
  • Now switch to forced logins in Internet Settings
    • Open Internet Settings | Security | Local Intranet Zone
    • Open Advanced | Custom Level | User Authentication | Logon
    • Set to: Prompt for username and password
  • Shut down browser and re-run same request
    Expected: you should now properly get the custom groups returned
// in `Configure()`
app.UseAuthentication(); 
app.UseAuthorization();

// Pick up Windows Authentication
app.Use(async (context, next) =>
{
    
    // First off ensure that user is authenticated with NTLM Auth
    if (!context.User.Identity.IsAuthenticated)
    {
        context.Response.StatusCode = 401;
        context.Response.Headers.Add("www-authenticate",
            new Microsoft.Extensions.Primitives.StringValues(new string[]
            {
                "Negotiate", "NTLM"
            }));

        await context.Response.WriteAsync("Unauthorized");
        return;
    }

    // check for your group here
    var isInGroup = context.User.IsInRole("FTDB-Technician");
    
    var groups = GetGroups(user.Identity as WindowsIdentity);
    await next();
    
});

// windows identity 
public static List<WindowsGroup> GetGroups(WindowsIdentity identity)
{
    var groups = new List<WindowsGroup>();

    foreach (var group in identity.Groups)
    {
        var sid = group.Value;
        var groupName = GetGroupNameBySid(sid);
        var (username, domain) = SplitUserDomain(groupName);
        
        if (groupName.StartsWith("NT AUTHORITY") ||
            groupName.StartsWith("BUILTIN") ||
            username == "Everyone" || 
            username == "None" ||
            username == "docker-users")
                
            continue;

        groups.Add(new WindowsGroup() {Name = username , Domain = domain + "," + sid + "," + identity.Name});
    }

    return groups;
}

public static string GetGroupNameBySid(string sid)
{
    try
    {
        return new SecurityIdentifier(sid).Translate(typeof(NTAccount)).ToString();
    }
    catch
    {
        return sid;
    }
}

Expected Behavior

Expecting to see the same user data regardless of whether the login is 'automatic' or manual.

@blowdart

This comment has been minimized.

Copy link
Member

@blowdart blowdart commented Oct 6, 2019

Which OS are you doing this under?

Does IsInRole still work for the "missing" groups? WindowsIdentity doesn't populate the roles in the way cookies or JWT does.

@RickStrahl

This comment has been minimized.

Copy link
Author

@RickStrahl RickStrahl commented Oct 6, 2019

Updated the original issue with additional details and hopefully simple repro steps.

This is on Windows. Same result using Kestrel directly or using IIS Express.

Does IsInRole still work

No it doesn’t work when the groups are missing in the claims when automatically logged in. IsInRole() works when explicitly logging in with the Browser Login Dialog.

Again - the problem isn't that it doesn't it work at all - it's that it doesn't work when automatically logged in vs. explicitly being logged in (via browser login dialog).

Different behavior for what should result in the same account being returned with the same exact attributes.

The questions that come to my mind is this:

  • Is Chrome sending a different user context that makes this different
    depending on whether it uses explicitly forced log in or auto-login?

  • Why would ASP.NET Care about above? Windows is picking up the same
    exact user SID, so why the different group memberships here?

Just for reference I'm pasting the request traces for each of the three types of requests I tend to make:

  • Http Client (explicitly provides uid/pwd on each request) - works
  • Edgium/Chrome with auto-login - doesn't work
  • Edgium/Chrome with auto-login disable forcing explicit login - works

Comparing request traces:

Works (from my HTTP Client):

GET https://localhost:5001/account/userstate HTTP/1.1
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3915.0 Safari/537.36 Edg/79.0.287.3
Authorization: Negotiate TlR22VNTUAADAAAAGAAYAHIAAADuAO4AigAAAAAAAABYAAAADgAOAFgAAAAMAAwAZgAAABAAEAB4AQAAFYKI4goA6kkAAAAPOU/w7N9731GENKwO8PjeZ3IAcwB0AHIAYQBoAGwAUgBBAFMAVwBJAE4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9G0vfxZ+ZEoaKItYU6VcUwEBAAAAAAAAGUz1Unt81QHDEL7Hd3KNbAAAAAACAAwAUgBBAFMAVwBJAE4AAQAMAFIAQQBTAFcASQBOAAQADABSAEEAUwBXAEkATgADAAwAUgBBAFMAVwBJAE4ABwAIABlM9VJ7fNUBBgAEAAIAAAAIADAAMAAAAAAAAAABAAAAACAAADmayz0EVIkTD7fPcH/qMN9F2ruH6zvDQe/tOeG8atdtCgAQAKBzSKrJJNkmrnS0V0gR6uIJABYASABUAFQAUAAvAFIAQQBTAFcASQBOAAAAAAAAAAAAAAAAADIZrqxQdxpVnwAAFnZCHMQ=
Host: localhost:5001

Doesn't work from Web Browser with Auto-Login:

GET https://localhost:5001/account/userstate HTTP/1.1
Host: localhost:5001
Connection: keep-alive
Authorization: Negotiate oXcwdaADCgEBoloEWE5UTE1TU1AAAwAAAAAAAABYAAAAAAAAAFgAAAAAAAAAWAAAAAAAAABYAAAAAAAAAFgAAAAAAAAAWAAAABXCiOIKAOpJAAAADxolNgli3b1m2NIyXf7ByJ+jEgQQAQAAAKV5VbhKCYaHAAAAAA==
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3915.0 Safari/537.36 Edg/79.0.287.3
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie:  ...

Works: In Chrome with Explicit Login Forced via Connection Settings

GET https://localhost:5001/account/userstate HTTP/1.1
Host: localhost:5001
Connection: keep-alive
Cache-Control: max-age=0
Authorization: Negotiate TlRMTVNTUAADAAAAGAAYAHIAAAD0APQAigAAAAAAAABYAAAADgAOAFgAAAAMAAwAZgAAABAAEAB+AQAAFYKI4goA6kkAAAAPZgzrjadKjWgGUp1BMao1I3IAcwB0AHIAYQBoAGwAUgBBAFMAVwBJAE4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8Y9uLIa66rbNfZR0GWdgJAEBAAAAAAAAaFOvLIF81QEt6YdfDA/KIAAAAAACAAwAUgBBAFMAVwBJAE4AAQAMAFIAQQBTAFcASQBOAAQADABSAEEAUwBXAEkATgADAAwAUgBBAFMAVwBJAE4ABwAIAGhTryyBfNUBBgAEAAIAAAAIADAAMAAAAAAAAAABAAAAACAAADmayz0EVIkTD7fPcH/qMN9F2ruH6zvDQe/tOeG8atdtCgAQAE+uI73nPkm2PKAT+XvLeuMJABwASABUAFQAUAAvAGwAbwBjAGEAbABoAG8AcwB0AAAAAAAAAAAAAAAAANOVqBHnP4Qh+m3IpazkgFM=
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3915.0 Safari/537.36 Edg/79.0.287.3
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: ...

Nothing here jumps out at me especially between the two Chrome requests (Max-age, Sec-Fetch-User?).

I also took these raw requests out of Chrome and explicitly stuck them into my HTTP Client (minus the Authorization header) and either of them work just fine, so I don't think the headers are the issue here. It looks like it's the actual user token/identity is off.

@blowdart

This comment has been minimized.

Copy link
Member

@blowdart blowdart commented Oct 7, 2019

You missed one thing I asked, does IsInRole() work?
Also does it work under IIS proper?

@RickStrahl

This comment has been minimized.

Copy link
Author

@RickStrahl RickStrahl commented Oct 7, 2019

Sorry must have edited it out. No it doesn’t work when the groups are missing in the claims when automatically logged in. IsInRole() works when explicitly logging in with the Browser Login Dialog.

IOW, it matches the behavior described above for claims.

IIS Express has the same behavior. haven't tried IIS - will give that a shot later.

@blowdart

This comment has been minimized.

Copy link
Member

@blowdart blowdart commented Oct 7, 2019

Weird.

Ok. @brentschmaltz you own WindowsIdentity and WindowsPrincipal. Do you have any idea what's happening here?

@RickStrahl

This comment has been minimized.

Copy link
Author

@RickStrahl RickStrahl commented Oct 9, 2019

Ok, I've set up a simple repo project that demonstrates the behavior I'm talking about here.

https://github.com/RickStrahl-Content/WindowsAuthWithGroupsIssue

Code is a single file here.

This project uses a super simple challenge and middleware endpoint to echo back what groups it sees after the login succeeds.

The two scenarios that differ are:

  • Intranet Auto-logon
  • Forced Browser Dialog Logon

As mentinoed above I get very different results for the auto-login vs. a forced browser login. The Auto-login is not returning any of my custom groups. In fact it's returning quite a different set of groups.

Note in all the shots below the User Sid is always the same - IOW, we're talking about the same user, on the same machine in the same environment, returning different results here!

Explicit Browser Login get the full list of Groups:

image

This works as it should - it's retrieving the list of custom accounts I've added.

Automatic Browser Login on Chrome, Edge, FireFox

image

IIS Proper

Finally I also published the site (locally) and used IIS to access it. Enabled Windows Authentication on the new site, then added the site to local Intranet groups.

With Auto-logon enabled once again - no groups are returned.

image

If I force logins via Internet Settings I again get all groups returned.

To clarfify the behavior occurs in Chrome, Edgium. FireFox is Ok because it doesn't support auto-login and always forces a login. Classic Edge fails to load the page (WTF?)...

@RickStrahl

This comment has been minimized.

Copy link
Author

@RickStrahl RickStrahl commented Oct 12, 2019

Any update on this?

@RickStrahl

This comment has been minimized.

Copy link
Author

@RickStrahl RickStrahl commented Oct 14, 2019

Ok - figured it out. Turns out it's sort of Operator Error, but related to the way Windows logons are apparently handled.

The issue is that I created new groups in Windows and then tried to use these groups in the application. These days, I don't reboot or log out of Windows very much so even though this discussion has dragged on for a week and more, I never logged out.

Apparently when you do Auto-Logon, Windows picks up a cached token of the user when the logon occurred. IOW, it looks like it shows only the groups that were present when I logged in. When logging in explicitly it refreshes credentials completely rather than re-using the cached credential.

When I finally decided to reboot my machine, the automatic login started returning the missing groups just fine. I verified if I add additional groups after the logon, they don't show up until I either log out or reboot.

This might be worthwhile to document in relation to groups with Windows Authentication.

Many thanks to Gabriel Luci, who actually suggested this in this StackOverflow issue I posted related to this issue.

@RickStrahl RickStrahl closed this Oct 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.