Unable to Make Windows Authentication in my Blazor Application

Looking for some help here please...

I created a blank application [Blazor Web Application .Net 8] for test (No DB / Data / New Page added) using Radzen Blazor Studio 1.25.0
Next I added "WINDOWS" Authentication (again by using Radzen Blazor Studio). Next (just to validate windows Auth Functionality), I added the following in my MainLayout.razor.cs

using System.Security.Principal;

		// Property to hold the Windows User ID
        public string WindowsUserID { get; private set; }

         // Inject IConfiguration for accessing app settings
        [Inject]
        public IConfiguration Configuration { get; set; }

        // Property to hold the environment label
        public string EnvironmentLabel { get; private set; }

        // Property to hold the background color for the environment label
        public string EnvironmentBackgroundColor { get; private set; }

        protected override void OnInitialized()
        {
            // Get the Windows identity of the current user
            WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent();

            // Extract the user ID from the Windows identity
            WindowsUserID = windowsIdentity != null ? windowsIdentity.Name : "Unknown";
            
            base.OnInitialized();
		}

And also I added the following in MainLayout.razor.... (to just reflect windows ID)

<RadzenStack AlignItems="AlignItems.Center" Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.End"  Gap="">
					<RadzenLabel Style="font-style: italic">You logged in as: @WindowsUserID</RadzenLabel>
				</RadzenStack>

~ Next I selected "Run" and then it indeed returning my windows id [DC1\AVIKMUKH] and then I "Published" in a folder and then moved that folder to my dev web server (new build Windows Server 2019). Over there I have IIS10 and also .Net runtime 8 etc... Currently All my Application pools are off and all sites (including IIS Default Web Site) are stopped.

So I created a new Application Pool - [AuthTest]. Created a new Site [AuthTest] and pointed to physical location of my site contents. It is running on port 8080.


The issue is - I am getting logged in As: IIS APPPOOL\AuthTest instead of my windows id [DC1\AVIKMUKH] despite I logged into the Server using my Windows ID. Also to note that my Windows ID is an Admin in Server location.

Next to that, I tried debugging in multiple ways
a) Changed port: 80 ; 85; 8082 etc etc... and same result everytime.
b) Tried changing Identity of AuthTest Application pool ti some other users, such as a service Account (admin) [DC1\SVC_ADMIN4478] ~ then that time, my site showing logged in as DC1\SVC_ADMIN4478
i.e. Whatever we put in Identity of App Pool that's what is used in the Site, instead of really getting Authenticated by Windows ID.

Any help would be highly appreciated... I was cluelessly browsing Microsoft's guideline for Windows
Auth in blazor. But not yet sure what I am missing?

Tried also to add the following segment in Web.config but no luck...

<system.web>
        <identity impersonate="false" />
			<authentication mode="Windows" />
			<httpCookies httpOnlyCookies="true" requireSSL="true" />
		<authorization>
		  <!-- uncomment to use windows auth -->
		   <deny users="?" />  
		</authorization>
    </system.web>

You might need to check this thread:

I tested the following:

  1. Created a new Blazor Web application.
  2. Displayed @Security.User.Name in the home page
  3. Published the application from Radzen Blazor Studio.
  4. Enabled Windows authentication in IIS settings.

Everything seems to work fine:

I didn't create a new app pool (used the default one) and didn't change any web.config.

Your application disables impersonation which could be somehow related.

1 Like

You are spot on @korchev . (Also many thanks to @enchev )
Using @Security.User.Name picks the correct Windows ID that I wanted. On the otherhand WindowsIdentity.GetCurrent() is picking Identity from the pool (IIS APPPOOL\AuthTest)
~ Therefore it is still a problem, because when I added a CRUD grid and inserted/ updated any record then its logging it (with SQL Default SUSER_NAME() for that column) as IIS APPPOOL\AuthTest where as I want this to be 'DC1\AVIKMUKH'

Advance Apology - I haven't fully gone through enchev's link to understand if he found any work around of it. The whole idea of Authenticating a site using Windows ID is to make sure everything runs with the context of that user, and he/she should be able to write (or not write) data in some grid/form depending on whether he's having/not having DB level permissions for his windows ID...

I guess you want impersonation in that case. This means that the server process runs on behalf of the current user.

Thanks again @korchev I am pretty sure you are pointing me right directions.

Apologies for bit of a panic here (being more of a database guy, my dictionary of Windows Authentication means people are using there Windows/AD accounts to login, other than that there is SQL Authentication with user id/password). In UI world too I assumed, enabling windows authentication means people will be accessing the site with the context of their windows ID and therefore if some table they dont write permission, then simply they will not be able to write anything even from UI (i.e, it will throw the error at DB side)... but apparently - I'm wrong! Site is working with the context of only 1 user, which is ApplicationPool$identity ~ so that's a major problem now. from the link that you shared - me too understand now that we need to incorporate some sort of impersonation.

But I wish MS site could have been little more user friendly.... I am totally unclear what they mean? where shall we apply such code? I used Radzen Blazor studio to build my app, there we used partial classes... in my model I have nearly 25 tables and therefore all in all almost 75 - 80 pages (Display Page, Add Page, Edit Page) each .razor and .razor.cs pages. Then we also have model, dbcontext etc etc... so how can I now manage to apply such advise as below one...

Any sample code/ example etc... will be highly appreciated in case you have.

I am afraid we don't have sample code for this case. This is the first time we encounter such requirement to be honest. I recommend you reconsider your approach to database access so it doesn't rely on windows user accounts.

Update: I found this StackOverflow thread c# - Impersonate Windows Identity in Entity Framework Core - Stack Overflow which says Entity Framework Core doesn't support your requirement out of the box. You can try overriding the SaveChanges method of the DBContext that Radzen Blazor Studio has generated and call the impersonation code there.

Thanks @korchev Really appreciate your efforts for helping. For 2 consecutive days, being stuck at 1 issue (Authentication) maybe I am at a loss totally... One thing I am sure that the requirement is so so common that there are tons of sites where people raised exact same questions but no where there is a clean clear answer I could derive. Its just browsing one website link to another and from there to another.

Last try (failed) I did today - Added the following code in my *Service.cs and tried executing SaveChangesAsCurrentUser(); for Add/delete/update...

~ Here, currentIdentity = _httpContextAccessor.HttpContext?.User.Identity as WindowsIdentity; ==> Throwing "NullReferenceException" - Object Reference Not set to as an instance of an object.
Not sure why and what to do to resolve....

        private readonly IHttpContextAccessor _httpContextAccessor;

        public AuthTestService(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public void SaveChangesAsCurrentUser()
        {
                // Get the current Windows identity from the HttpContext
                WindowsIdentity currentIdentity = null;
                try
                {
                    currentIdentity = _httpContextAccessor.HttpContext?.User.Identity as WindowsIdentity;
                }
                catch (Exception ex)
                {
                    // Log or handle the exception as needed
                    Console.WriteLine($"Error getting Windows identity: {ex.Message}");
                    // Optionally rethrow the exception
                    throw;
                }

                if (currentIdentity != null)
                {
                    // Run your code with the current user's identity
                    WindowsIdentity.RunImpersonated(currentIdentity.AccessToken, () =>
                    {
                        // Call the SaveChanges method here
                        Context.SaveChanges();
                    });
                }
                else
                {
                    // Handle the case where the current user's identity is not available
                    // (e.g., if Windows Authentication is not enabled)
                    throw new InvalidOperationException("Windows Authentication is required.");
                }

        }

Last but not the least, I want ask a dumb question may be. You mentioned "...I recommend you reconsider your approach to database access so it doesn't rely on windows user accounts." --- How would you do it differently? I mean how would you manage role based permission for various users? Purely from Frontend and not at all at Backend? How - I am curious to hear?

I would use ASP.NET Core Identity security which is probably the most commonly used option for securing ASP.NET applications. Radzen Blazor Studio provides support for it as well: ASP.NET Core Identity | Radzen Blazor Studio

I see. No unfortunately that's absolutely not our scenario. We aren't creating any public website, instead we are making site for one of application which is internal to the organization and hosted within Intranet and internal Infrastructure. So ofcourse we won't further overhead another userid password based user profile maintenance.

But anyways, just for my curiosity i was still watching that video and I doubt that too solves all problems. For example, over there they showed how to prevent a role from accessing 'order' page.... Nice. But what if I want users to be able to view all order but not be able to delete (for that i have a different role let's say). Then i dont see how it could be done (probably we will have to duplicate same pages twice, one without delete button and another with delete button and then according give access to specific pages... Oh my goodness!). I am still convinced DB is the best and most secured place to restrict user roles. No offense please.

To a database engineer this probably looks as the best approach. I would disagree though as database security can't mix with UI security - hide pages that are not needed for example.

This is trivial to implement by hiding the delete button if the user doesn't belong to a certain role - <RadzenButton Visible=@Security.IsInRole("Admin")>

I tested impersonation and it seems to work just fine. Here is my test code:

        [Inject]
        IHttpContextAccessor ContextAccessor { get; set;}
        
        WindowsIdentity windowsIdentity;

        protected override void OnInitialized()
        {
            var user = ContextAccessor.HttpContext.User.Identity as WindowsIdentity;
            WindowsIdentity.RunImpersonated(user.AccessToken, () => 
            {
                windowsIdentity = WindowsIdentity.GetCurrent();
            });
        }

I display @windowsIdentity.Name in the page.

After deployment it shows this.

If I remove the RunImpersonated code:

        protected override void OnInitialized()
        {
            //var user = ContextAccessor.HttpContext.User.Identity as WindowsIdentity;
            //WindowsIdentity.RunImpersonated(user.AccessToken, () => 
            //{
                windowsIdentity = WindowsIdentity.GetCurrent();
            //});
        }

I see something like you:

So WindowsIdentity.RunImpersonated seems to correctly impersonate and WindowsIdentity.GetCurrent() returns what you seem to expect. I don't know if this helps you in any way as I don't know what this WindowsIdentity would be used for.

Thanks again @korchev We definitely have made some progress but not all done.

Exactly that same code - showing me the Windows ID when browsing via IIS...

But At design time - its throwing error -

I am curious to know whether you too facing same issue?

Although that's might not sound like a big concern (ultimately if it works from IIS) but in reality, there is some catch... Even though it's displaying the Windows ID on Index Page at page load in IIS but then if you have another other page to do any CRUD etc. it's not at all moving to that page anymore from Index and upon further check (in event log) I realized that still actually the following line isn't working... that's where the null reference exception coming from

  var user = ContextAccessor.HttpContext.User.Identity as WindowsIdentity;

If by any chance at your side everything working, would you mind sharing your solution so I can test at my side? (Or else I can provide my solution and request you to run once at your side?)

The design time error is easily fixable by using @windowsIdentity?.Name. However I don't think it causes the problem in your application. It seems that ContextAccessor.HttpContext is null for some reason. I am sending you my test application. It shows another way of accessing the current user:

        [CascadingParameter]
        public Task<AuthenticationState> AuthenticationState { get; set; }
            
        protected override async Task OnInitializedAsync()
        {
            var authenticationState = await AuthenticationState;
            var user = authenticationState.User.Identity as WindowsIdentity;

            //var user = ContextAccessor.HttpContext.User.Identity as WindowsIdentity;
            WindowsIdentity.RunImpersonated(user.AccessToken, () => 
            {
                windowsIdentity = WindowsIdentity.GetCurrent();
            });
        }

Both approaches work as expected at my side producing the same result after impersonation.

Net8Server.zip (326.9 KB)

1 Like

Apologies for my delayed response due to a business trip in between.
I went through your code/solution. I mean - Indeed that's working in a way to display the Correct Windows User ID (Despite IIS pool is running with Service Account). So a major win... But I think still I am far from the solution.
Because ultimately, what I want is something like below, on my Service.cs page where all the Create/Update/Remove tasks are there for all Blazor pages. and over there instead of Context.SaveChanges() I need to replace it by SaveChangesAsCurrentUser

    public void SaveChangesAsCurrentUser()

{
.....
....
var authenticationState = await AuthenticationState;
var user = authenticationState.User.Identity as WindowsIdentity;
//var user = ContextAccessor.HttpContext.User.Identity as WindowsIdentity;
WindowsIdentity.RunImpersonated(user.AccessToken, () =>
{
Context.SaveChanges();
});
....

~ But the problem is - On Service.Cs I am neither getting AuthenticationState not HttpContext.

Any Advise please?

Did you try injecting those as constructor parameters?

No. The code I've shared with the sample application is the only thing I can think of.

"Did you try injecting those as constructor parameters?"

No. I didn't... What you mean is - in such case we need to go and change each and every blazor pages (I have 47+) to fetch authentication state and then user and subsequently apply that as param?
I want to rather centralize it somehow if possible

No, I meant to inject those in the service constructor. I would start with HttpContextAccessor. You probably have a single service for your database. Or you can extend the EF database context and add it there. Then override SaveChanges().

I am still not quite sure what you are trying to do so I can't be more specific. We have been working on Radzen for 7 years and this is the first time anybody requests such authentication. I am not even sure if this can be implemented at all in Blazor.