Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ASP.NET Core Active Directory Integration

Active Directory Authentication

This will provide an example of integrating Active Directory authentication in an ASP.NET Core app.

Note, you'll need to be running on a Windows domain with Visual Studio debugging in IIS Express for this to work.

Setup

In launchSettings.json, you'll want to modify iisSettings by turning on windowsAuthentication:

launchSettings.json

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": false,
    "iisExpress": {
      "applicationUrl": "http://localhost:5000"
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "FullstackOverview.Web": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Identity Project

Create a netcoreapp2.2 class library (I tend to name mine {Project}.Identity).

You'll need to add the following NuGet packages to this library:

  • Microsoft.AspNetCore.Http
  • Microsoft.Extensions.Configuration.Abstractions
  • Microsoft.Extensions.Configuration.Binder
  • System.DirectoryServices
  • System.DirectoryServices.AccountManagement

Here is the infrastructure of this class library:

  • Extensions
    • IdentityExtensions.cs
    • MiddlewareExtensions.cs
  • AdUser.cs
  • AdUserMiddleware.cs
  • AdUserProvider.cs
  • IUserProvider.cs

AdUser.cs

I use this class so I can create a Mock implementation of this library for when I'm building outside of a domain environment. This relieves me of the dependency on UserPrincipal.

using System;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;

namespace Project.Identity
{
    public class AdUser
    {
        public DateTime? AccountExpirationDate { get; set; }
        public DateTime? AccountLockoutTime { get; set; }
        public int BadLogonCount { get; set; }
        public string Description { get; set; }
        public string DisplayName { get; set; }
        public string DistinguishedName { get; set; }
        public string Domain { get; set; }
        public string EmailAddress { get; set; }
        public string EmployeeId { get; set; }
        public bool? Enabled { get; set; }
        public string GivenName { get; set; }
        public Guid? Guid { get; set; }
        public string HomeDirectory { get; set; }
        public string HomeDrive { get; set; }
        public DateTime? LastBadPasswordAttempt { get; set; }
        public DateTime? LastLogon { get; set; }
        public DateTime? LastPasswordSet { get; set; }
        public string MiddleName { get; set; }
        public string Name { get; set; }
        public bool PasswordNeverExpires { get; set; }
        public bool PasswordNotRequired { get; set; }
        public string SamAccountName { get; set; }
        public string ScriptPath { get; set; }
        public SecurityIdentifier Sid { get; set; }
        public string Surname { get; set; }
        public bool UserCannotChangePassword { get; set; }
        public string UserPrincipalName { get; set; }
        public string VoiceTelephoneNumber { get; set; }
        
        public static AdUser CastToAdUser(UserPrincipal user)
        {
            return new AdUser
            {
                AccountExpirationDate = user.AccountExpirationDate,
                AccountLockoutTime = user.AccountLockoutTime,
                BadLogonCount = user.BadLogonCount,
                Description = user.Description,
                DisplayName = user.DisplayName,
                DistinguishedName = user.DistinguishedName,
                EmailAddress = user.EmailAddress,
                EmployeeId = user.EmployeeId,
                Enabled = user.Enabled,
                GivenName = user.GivenName,
                Guid = user.Guid,
                HomeDirectory = user.HomeDirectory,
                HomeDrive = user.HomeDrive,
                LastBadPasswordAttempt = user.LastBadPasswordAttempt,
                LastLogon = user.LastLogon,
                LastPasswordSet = user.LastPasswordSet,
                MiddleName = user.MiddleName,
                Name = user.Name,
                PasswordNeverExpires = user.PasswordNeverExpires,
                PasswordNotRequired = user.PasswordNotRequired,
                SamAccountName = user.SamAccountName,
                ScriptPath = user.ScriptPath,
                Sid = user.Sid,
                Surname = user.Surname,
                UserCannotChangePassword = user.UserCannotChangePassword,
                UserPrincipalName = user.UserPrincipalName,
                VoiceTelephoneNumber = user.VoiceTelephoneNumber
            };
        }
        
        public string GetDomainPrefix() => DistinguishedName
            .Split(',')
            .FirstOrDefault(x => x.ToLower().Contains("dc"))
            .Split('=')
            .LastOrDefault()
            .ToUpper();
    }
}

IUserProvider.cs

I use this interface so that I can create an additional provider in a mock library that implements this interface so I don't have to be connected to an AD domain while at home.

using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;

namespace Project.Identity
{
    public interface IUserProvider
    {
        AdUser CurrentUser { get; set; }
        bool Initialized { get; set; }
        Task Create(HttpContext context, IConfiguration config);
        Task<AdUser> GetAdUser(IIdentity identity);
        Task<AdUser> GetAdUser(string samAccountName);
        Task<AdUser> GetAdUser(Guid guid);
        Task<List<AdUser>> GetDomainUsers();
        Task<List<AdUser>> FindDomainUser(string search);
    }
}

AdUserProvider.cs

Because you're using Windows authentication, the HttpContext will contain an IIdentity of the user logged into the domain that is accessing the web app. Because of this, we can leverage the System.DirectoryServices.AccountManagement library to pull their UserPrincipal.

using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Project.Identity.Extensions;

namespace Project.Identity
{
    public class AdUserProvider : IUserProvider
    {
        public AdUser CurrentUser { get; set; }
        public bool Initialized { get; set; }
        
        public async Task Create(HttpContext context, IConfiguration config)
        {
            CurrentUser = await GetAdUser(context.User.Identity);
            Initialized = true;
        }
        
        public Task<AdUser> GetAdUser(IIdentity identity)
        {
            return Task.Run(() =>
            {
                try
                {
                    PrincipalContext context = new PrincipalContext(ContextType.Domain);
                    UserPrincipal principal = new UserPrincipal(context);
                    
                    if (context != null)
                    {
                        principal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, identity.Name);
                    }
                    
                    return AdUser.CastToAdUser(principal);
                }
                catch (Exception ex)
                {
                    throw new Exception("Error retrieving AD User", ex);
                }
            });
        }
        
        public Task<AdUser> GetAdUser(string samAccountName)
        {
            return Task.Run(() =>
            {
                try
                {
                    PrincipalContext context = new PrincipalContext(ContextType.Domain);
                    UserPrincipal principal = new UserPrincipal(context);
                    
                    if (context != null)
                    {
                        principal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, samAccountName);
                    }
                    
                    return AdUser.CastToAdUser(principal);
                }
                catch (Exception ex)
                {
                    throw new Exception("Error retrieving AD User", ex);
                }
            });
        }
        
        public Task<AdUser> GetAdUser(Guid guid)
        {
            return Task.Run(() =>
            {
                try
                {
                    PrincipalContext context = new PrincipalContext(ContextType.Domain);
                    UserPrincipal principal = new UserPrincipal(context);
                    
                    if (context != null)
                    {
                        principal = UserPrincipal.FindByIdentity(context, IdentityType.Guid, guid.ToString());
                    }
                    
                    return AdUser.CastToAdUser(principal);
                }
                catch (Exception ex)
                {
                    throw new Exception("Error retrieving AD User", ex);
                }
            });
        }
        
        public Task<List<AdUser>> GetDomainUsers()
        {
            return Task.Run(() =>
            {
                PrincipalContext context = new PrincipalContext(ContextType.Domain);
                UserPrincipal principal = new UserPrincipal(context);
                principal.UserPrincipalName = "*@*";
                principal.Enabled = true;
                PrincipalSearcher searcher = new PrincipalSearcher(principal);
                
                var users = searcher
                    .FindAll()
                    .AsQueryable()
                    .Cast<UserPrincipal>()
                    .FilterUsers()
                    .SelectAdUsers()
                    .OrderBy(x => x.Surname)
                    .ToList();
                    
                return users;
            });
        }
        
        public Task<List<AdUser>> FindDomainUser(string search)
        {
            return Task.Run(() =>
            {
                PrincipalContext context = new PrincipalContext(ContextType.Domain);
                UserPrincipal principal = new UserPrincipal(context);
                principal.SamAccountName = $"*{search}*";
                principal.Enabled = true;
                PrincipalSearcher searcher = new PrincipalSearcher(principal);
                
                var users = searcher
                    .FindAll()
                    .AsQueryable()
                    .Cast<UserPrincipal>()
                    .FilterUsers()
                    .SelectAdUsers()
                    .OrderBy(x => x.Surname)
                    .ToList();
                    
                return users;
            });
        }
    }
}

AdUserMiddleware.cs

Custom middleware for creating the IUserProvider instance registered with Dependency Injection (see Startup Configuration below).

using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;

namespace Project.Identity
{
    public class AdUserMiddleware
    {
        private readonly RequestDelegate next;
        
        public AdUserMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        
        public async Task Invoke(HttpContext context, IUserProvider userProvider, IConfiguration config)
        {
            if (!(userProvider.Initialized))
            {
                await userProvider.Create(context, config);
            }
            
            await next(context);
        }
    }
}

IdentityExtensions.cs

Utility extensions for only pulling users with a Guid, and casting UserPrincipal to AdUser.

using System.DirectoryServices.AccountManagement;
using System.Linq;

namespace Project.Identity.Extensions
{
    public static class IdentityExtensions
    {
        public static IQueryable<UserPrincipal> FilterUsers(this IQueryable<UserPrincipal> principals) =>
            principals.Where(x => x.Guid.HasValue);
            
        public static IQueryable<AdUser> SelectAdUsers(this IQueryable<UserPrincipal> principals) =>
            principals.Select(x => AdUser.CastToAdUser(x));
    }
}

MiddlewareExtensions.cs

Utility extension for making middleware registration in Startup.cs easy.

using Project.Identity;

namespace Microsoft.AspNetCore.Builder
{
    public static class MiddlewareExtensions
    {
        public static IApplicationBuilder UseAdMiddleware(this IApplicationBuilder builder) =>
            builder.UseMiddleware<AdUserMiddleware>();
    }
}

Startup Configuration

To access the current user within the application, in the Startup.cs class of your ASP.NET Core project, you need to register an IUserProvider of type AdUserProvider with Dependency Injection with a Scoped lifecycle (per HTTP request):

public void ConfigureServices(IServiceCollection services)
{
    // Additional service registration
    services.AddScoped<IUserProvider, AdUserProvider>();
    // Additional service registration
}

You then need to add the AdUserMiddleware to the middleware pipeline:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // Additional Configuration
    app.UseAdMiddleware();
    // Additional Configuration
}

Accessing the Current User

Because the IUserProvider is configured in the middleware pipeline, and is registered with Dependency Injection, you can setup an API point to interact with the registered instance:

IdentityController.cs

[Route("api/[controller]")]
public class IdentityController : Controller
{
    private IUserProvider provider;

    public IdentityController(IUserProvider provider)
    {
        this.provider = provider;
    }

    [HttpGet("[action]")]
    public async Task<List<AdUser>> GetDomainUsers() => await provider.GetDomainUsers();

    [HttpGet("[action]/{search}")]
    public async Task<List<AdUser>> FindDomainUser([FromRoute]string search) => await provider.FindDomainUser(search);

    [HttpGet("[action]")]
    public AdUser GetCurrentUser() => provider.CurrentUser;
}
@AlessandroDalMas

This comment has been minimized.

Copy link

@AlessandroDalMas AlessandroDalMas commented May 10, 2019

Thanks for your article, worked perfectly but:

app.UseAdMiddleare();

I think there is a mispelling error:

app.UseAdMiddleware();

@JaimeStill

This comment has been minimized.

Copy link
Owner Author

@JaimeStill JaimeStill commented Jun 3, 2019

Fixed, thanks!

@d4dpkrajput

This comment has been minimized.

Copy link

@d4dpkrajput d4dpkrajput commented Jun 17, 2019

In Starup.cs > public void ConfigureServices(IServiceCollection services)
You forgot " services.AddSession();". IT was giving error that
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Session.ISessionStore' while attempting to activate 'Microsoft.AspNetCore.Session.SessionMiddleware'.
at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)

@d4dpkrajput

This comment has been minimized.

Copy link

@d4dpkrajput d4dpkrajput commented Jun 17, 2019

Thanks for the code !!!

@jgranger36

This comment has been minimized.

Copy link

@jgranger36 jgranger36 commented Jul 3, 2019

I am struggling with getting this working. I am getting an error. See below. Is there something i need to pass in to get the current app user?

_An unhandled exception occurred while processing the request.
ArgumentNullException: Value cannot be null.
Parameter name: identityValue
System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext context, Type principalType, IdentityType identityType, string identityValue)

Exception: Error retrieving AD User
CoreIdentity.AdUserProvider+<>c__DisplayClass9_0.b__0() in AdUserProvider.cs, line 42_

@Grey007

This comment has been minimized.

Copy link

@Grey007 Grey007 commented Aug 1, 2019

Out of interest, if you were a .core app within a Linux Container. Would this work exactly the same?

@codedmind

This comment has been minimized.

Copy link

@codedmind codedmind commented Aug 5, 2019

Hello, do you have something similar but for asp mvc?

Thanks

@monicatudor

This comment has been minimized.

Copy link

@monicatudor monicatudor commented Sep 9, 2019

Hi, thanks for the code. Everything is working.

I just had to add in my startup:

services.AddAuthentication(IISDefaults.AuthenticationScheme);

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Oct 11, 2019

Is it possible to get any help in order to connect this back-end with angular? Back-end part from this tutorial works great, I am able to get AD user, but whenever I try to call some of my methods in back-end from Angular app, I get 401 Unauthorized message... so I am not sure what else I need to do... Thanks

@JaimeStill

This comment has been minimized.

Copy link
Owner Author

@JaimeStill JaimeStill commented Oct 11, 2019

Is it possible to get any help in order to connect this back-end with angular? Back-end part from this tutorial works great, I am able to get AD user, but whenever I try to call some of my methods in back-end from Angular app, I get 401 Unauthorized message... so I am not sure what else I need to do... Thanks

@sgsnikola1 check out this other gist for setting up the Angular stuff.

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Oct 14, 2019

@JaimeStill, I did take a look but I am unable to make a connection between angular and net core backend. I am getting 401 (Unauthorized) error when trying to call SyncUser on startup of angular app. I am also getting CORS error. CORS has been correctly setup because its working well if I set windowsAuthentication: false inside launchSettings.json. Any suggestion what to check? Do note that I am trying to set this up on my local machine and that I am trying to set this all up by NOT using EntityFramework. Thanks

@JaimeStill

This comment has been minimized.

Copy link
Owner Author

@JaimeStill JaimeStill commented Oct 15, 2019

@sgsnikola1 Are you running your Angular app separate from your ASP.NET Core back end? All of this is setup for an app that has Angular hosted within the ASP.NET Core app. If you can figure out your CORS issues, it should still work well, but is beyond the scope of what I'm presenting here.

@monicatudor

This comment has been minimized.

Copy link

@monicatudor monicatudor commented Oct 15, 2019

@JaimeStill, I did take a look but I am unable to make a connection between angular and net core backend. I am getting 401 (Unauthorized) error when trying to call SyncUser on startup of angular app. I am also getting CORS error. CORS has been correctly setup because its working well if I set windowsAuthentication: false inside launchSettings.json. Any suggestion what to check? Do note that I am trying to set this up on my local machine and that I am trying to set this all up by NOT using EntityFramework. Thanks

Did you try to add in your startup where you configure cors .WithOrigins("http://localhost:4200")? Or maybe just add the API under IIS and start it from there rather than from your visual studio?

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Oct 16, 2019

@JaimeStill, yes, like you said, I have two separate projects, angular and .net core back end.
@monicatudor, yes I have already configured the cors. If I set windowsAuthentication: false inside launchSettings.json, both apps "see" each other and everything is working, no cors issues... Anyhow, still working on a solution.

Thanks for your help.

@danielts86

This comment has been minimized.

Copy link

@danielts86 danielts86 commented Oct 30, 2019

@JaimeStill, yes, like you said, I have two separate projects, angular and .net core back end.
@monicatudor, yes I have already configured the cors. If I set windowsAuthentication: false inside launchSettings.json, both apps "see" each other and everything is working, no cors issues... Anyhow, still working on a solution.

Thanks for your help.

My fix:

  1. in the angular IdentityService I add to get method { withCredentials: true}:
    this.http.get(this.baseUrl + 'syncUser', { withCredentials: true}).subscribe(data => this.currentUser.next(data));
  2. In the Configure method inside Startup.cs add on app.UseCors another policy AllowCredentials()
    app.UseCors(configurePolicy => configurePolicy.WithOrigins("http://localhost:4200")
    .AllowAnyMethod()
    .AllowCredentials()
    .AllowAnyHeader());
@JaimeStill

This comment has been minimized.

Copy link
Owner Author

@JaimeStill JaimeStill commented Oct 30, 2019

@danielts86 that's awesome, thanks for the setup with disconnected projects!

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 1, 2019

@danielts86, thanks for provided solution, its working on my side too. Cant believe it was that simple :)

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 1, 2019

I came across another problem, I am checking is my AD user in a certain group. Its inside GetAdUser method. I am not sure what to return in case if my user is not group member...

  public Task<AdUser> GetAdUser(IIdentity identity)
        {
        var groupName = "bbsAdmins";

        return Task.Run(() =>
        {
            try
            {
                PrincipalContext context = new PrincipalContext(ContextType.Domain);
                UserPrincipal principal = new UserPrincipal(context);
                PrincipalSearchResult<Principal> groups = principal.GetAuthorizationGroups();

                if (context != null)
                {
                    principal = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, identity.Name);
                    groups = principal.GetAuthorizationGroups();
                }

                if (groups.OfType<GroupPrincipal>().Any(g => g.Name.Equals(groupName, StringComparison.OrdinalIgnoreCase)))
                {
                    return AdUser.CastToAdUser(principal);
                }
                else
                {
                    // what should I return here?
                }

            }
            catch (Exception ex)
            {
                throw new Exception("Error retrieving AD User", ex);
            }
        });
    }
@danielts86

This comment has been minimized.

Copy link

@danielts86 danielts86 commented Nov 1, 2019

@danielts86 that's awesome, thanks for the setup with disconnected projects!

Sure :)
I also found you can use HttpInterceptor:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpInterceptor, HttpEvent} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class WinAuthInterceptor implements HttpInterceptor{
constructor(){}
intercept(req: HttpRequest, next: HttpHandler): Observable<HttpEvent> {
req = req.clone({
withCredentials: true
});
return next.handle(req);
}
--- And add it app module:
providers: [{provide: HTTP_INTERCEPTORS, useClass: int.WinAuthInterceptor, multi: true}
Now you can remove the { withCredentials: true}

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 6, 2019

@danielts86,
I have implemented your solution and its working well for all GET methods, but if I try calling POST method, I am getting CORS error as well the error 401 (Unauthorized)... Thanks

@danielts86

This comment has been minimized.

Copy link

@danielts86 danielts86 commented Nov 9, 2019

@danielts86,
I have implemented your solution and its working well for all GET methods, but if I try calling POST method, I am getting CORS error as well the error 401 (Unauthorized)... Thanks

You probably not send your object in JSON format, just use JSON.stringify(object)

@mca93

This comment has been minimized.

Copy link

@mca93 mca93 commented Nov 12, 2019

Hello, please assist. I'm having an exception. See the trace below:
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'SB_AD.Idenetity.IUserProvider' while attempting to Invoke middleware 'SB_AD.Idenetity.AdUserMiddleware'.
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.GetService(IServiceProvider sp, Type type, Type middleware)

InvalidOperationException: Unable to resolve service for type 'SB_AD.Idenetity.IUserProvider' while attempting to Invoke middleware 'SB_AD.Idenetity.AdUserMiddleware'.
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.GetService(IServiceProvider sp, Type type, Type middleware)
lambda_method(Closure , object , HttpContext , IServiceProvider )
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass4_1.b__2(HttpContext context)
Microsoft.AspNetCore.CookiePolicy.CookiePolicyMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 12, 2019

@danielts86, thanks, I will give it a try.

@mca93, I believe you forgot to register dependencies inside ConfigureServices of your startup.cs. Check do you have this line:
services.AddScoped<IUserProvider, AdUserProvider>();

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 12, 2019

@danielts86, after stringify-ing my form data, I am able to hit the controller without errors but all data received by the controller is null...
part of my controller:
public IActionResult Update([FromForm] User userData) { }

User model is ok. I tried changing from "FromForm" to "FromBody" but then I am getting same 401 Unauthorized error. Data sent from the angular are in valid JSON format, like this
{"userName":"john@doe.com","firstName":"John","lastName":"Doe", "status":true}

I also tried sending data via postman. Its working well If I send data as x-www-form-urlencoded and pass key/value data, but if I try setting header as applicaton/x-www-form-urlencoded inside Angular app, sent data are still null, (with or without JSON.stringify function). I really dont know what else to try...

@danielts86

This comment has been minimized.

Copy link

@danielts86 danielts86 commented Nov 12, 2019

@sgsnikola1 -

  1. Don't use [FromForm] use [FromBody]
  2. Please share your the Angular Component submit method
  3. Are use using [HttpPost] attribute?
  4. Try to post without argument
  5. For Postman you also need to use Authorization NTLM
  6. Are you set Header Content-Type to application/json?
@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 13, 2019

@danielts86

  1. I tried using [FromBody], I am getting 400 (Bad Request) error.
  2. I have already posted a question on SO but no result... here are all the details: https://stackoverflow.com/questions/58748706/issue-with-the-post-and-net-core-web-api-401-unauthorized-cors
  3. Yes I do
  4. Tried, still the same.
  5. Yes I know, I added it and it was working without issues.
  6. I am getting 401 Unauthorized if I set to application/json. I also tried to set to "application/x-www-form-urlencoded" as well to to send it without header, I am getting 400 (Bad Request).

Thank you.

## UPDATE ##
I was able to hit my controller and POST data after I did following changes:

  1. Changed controller attribute from "FromForm" to "FromBody".
  2. Changed angular post method to the following:
 headerOptions = new HttpHeaders({ 'Content-Type': 'application/json' 
 });

 update(data: Form) {
 return this.http.post(this.apiUrl + '/data/Update', JSON.stringify(data), { headers:  this.headerOptions });
 }
  1. Set "anonymousAuthentication: true" inside my launchSettings.json.

The new problem pops out because I can set this to true only for testing purposes, because I am using windows AD authentication. The problem I have now is that my user is null and rest of the app is not working because I am calling "public Task GetAdUser(IIdentity identity)" on application startup...

## FINAL SOLUTION ##

I have managed to solve the problem of null user by following answer from here: https://stackoverflow.com/questions/46084851/windows-and-anonymous-authentication-in-net-core-2-0/

People should just pay attention about the order of implementing this middleware, here is my order inside Configure method on Startup.cs

            app.UseCors("CorsPolicy");
            app.UseAnonMiddleware();
            app.UseAdMiddleware();
            app.UseMvc();

Thanks

@mca93

This comment has been minimized.

Copy link

@mca93 mca93 commented Nov 18, 2019

@danielts86, thanks, I will give it a try.

@mca93, I believe you forgot to register dependencies inside ConfigureServices of your startup.cs. Check do you have this line:
services.AddScoped<IUserProvider, AdUserProvider>();

Hi @danielts86 thqnks for the response. Find below my ConfigureServices Method.

image

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 18, 2019

@mca93,
Why are you registering services.AddScoped<IUserProvider, AdUserProvider>(); inside your cookie configuration? Try placing it below services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); line.

Also I just double-checked your error message, it say "SB_AD.Idenetity.IUserProvider", IdenEtity, instead of Identity, you have an extra "E", so please correct that also.

@mca93

This comment has been minimized.

Copy link

@mca93 mca93 commented Nov 20, 2019

@sgsnikola1
Thanks for your help. It is working perfectly. Solution was to register services.AddScoped<IUserProvider, AdUserProvider>() outside the cookie configuration.

IdenEtity - is because i named my namespace like that way.

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 20, 2019

@mca93, ok great.
Regarding the typo, I know, but I suggest to correct it because of future use.
Regards

@mca93

This comment has been minimized.

Copy link

@mca93 mca93 commented Nov 24, 2019

@danielts86

This comment has been minimized.

Copy link

@danielts86 danielts86 commented Nov 26, 2019

@sgsNone - Can you share your AnonMiddleware class and usage

@sgsnikola1

This comment has been minimized.

Copy link

@sgsnikola1 sgsnikola1 commented Nov 27, 2019

@danielts86, here you go.
class

   public class AnonymousMiddleware
    {
        private readonly RequestDelegate next;

        public AnonymousMiddleware(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            if (context.User.Identity.IsAuthenticated)
            {
                await next(context);
                return;
            }

            await context.ChallengeAsync("Windows");
        }
    }

extension

public static IApplicationBuilder UseAnonMiddleware(this IApplicationBuilder builder) =>
            builder.UseMiddleware<AnonymousMiddleware>();

startup

app.UseCors("CorsPolicy");
app.UseAnonMiddleware();
app.UseAdMiddleware();
app.UseMvc();
@Vrankela

This comment has been minimized.

Copy link

@Vrankela Vrankela commented Feb 7, 2020

I am struggling with getting this working. I am getting an error. See below. Is there something i need to pass in to get the current app user?

_An unhandled exception occurred while processing the request.
ArgumentNullException: Value cannot be null.
Parameter name: identityValue
System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext context, Type principalType, IdentityType identityType, string identityValue)

Exception: Error retrieving AD User
CoreIdentity.AdUserProvider+<>c__DisplayClass9_0.b__0() in AdUserProvider.cs, line 42_

I have the exact same problem but only when I deploy the project to IIS server, locally however this works just fine. Has anyone had this issue and solved it?

@JaimeStill

This comment has been minimized.

Copy link
Owner Author

@JaimeStill JaimeStill commented Feb 7, 2020

@Vrankela, did you ensure that Windows Authentication is enabled and Anonymous Authentication is disabled on your IIS server? And that you are accessing the deployed app from a domain account? The error you're showing indicates that the user identity is not being provided to your app.

@Vrankela

This comment has been minimized.

Copy link

@Vrankela Vrankela commented Feb 7, 2020

@Vrankela, did you ensure that Windows Authentication is enabled and Anonymous Authentication is disabled on your IIS server? And that you are accessing the deployed app from a domain account? The error you're showing indicates that the user identity is not being provided to your app.

Yes Jamie, all of that. I even tested how it behaves when I just leave the windows authentication in launchSettings.json and remove the dependency injection from the two methods in Startup.cs. Then the browser just prompts me for my domain password and my app runs normally. But I really need your implementation as it allows me to further develop on it and filter through authorized users and user groups...

@hattabatatta

This comment has been minimized.

Copy link

@hattabatatta hattabatatta commented Mar 18, 2020

Sorry for a maybe stupid question but how to implement a "filter" for user groups, based on MVC Controllers?
Like "SuperAdminController" is only accessible by the AD group "SuperAdmins" and a "NormalADUserController" which can be accessed by every user from the AD?
Like an authentication within an authentication ... authentiception :D

I hope you know what I mean :)

Every controller (also API controllers) will be first check if the call comes from an AD user, which is fine, but I need also want to have "user rights"

@JaimeStill

This comment has been minimized.

Copy link
Owner Author

@JaimeStill JaimeStill commented Mar 18, 2020

@hattabatatta perhaps this StackOverflow answer would be helpful?

@hattabatatta

This comment has been minimized.

Copy link

@hattabatatta hattabatatta commented Mar 18, 2020

@hattabatatta perhaps this StackOverflow answer would be helpful?

Many thx for your quick answer
I´ll take a look at it ... I thought on dependency injection into contollers in first hand but if this will work ... also welcomed :)

@travbeamo

This comment has been minimized.

Copy link

@travbeamo travbeamo commented Aug 11, 2020

Excellent work.

Do you know if/how this could possibly be converted into an external provider authentication in ASP.NET Core, where Startup.cs for the project would include something like:

services.AddAuthentication().AddActiveDirectory(options => {
  options.DomainName = Configuration["Authentication:ActiveDirectory:DomainName"];
});
@JaimeStill

This comment has been minimized.

Copy link
Owner Author

@JaimeStill JaimeStill commented Aug 11, 2020

Excellent work.

Do you know if/how this could possibly be converted into an external provider authentication in ASP.NET Core, where Startup.cs for the project would include something like:

services.AddAuthentication().AddActiveDirectory(options => {
  options.DomainName = Configuration["Authentication:ActiveDirectory:DomainName"];
});

@travbeamo I can't imagine this being too difficult to implement. That said, my bandwidth is very limited at the moment. If you're able to figure out an implementation for this, it would be awesome if you could share.

@MostafaNouri

This comment has been minimized.

Copy link

@MostafaNouri MostafaNouri commented Aug 26, 2020

I'm using the IIS Express to deploy your nice project. When I use my local user (not administrator) the browser pop-up login form shows up but I want my custom login page. is it possible or not? I know that my question is somehow irrelevant to this context but I really need to know how can I do that.

@ajeyaprakash148

This comment has been minimized.

Copy link

@ajeyaprakash148 ajeyaprakash148 commented Oct 1, 2020

I am getting below error.

System.DirectoryServices.AccountManagement.PrincipalServerDownException: 'The server could not be contacted.'

is there any help? whether we need to pass domain, user, pwd details?
PrincipalContext context = new PrincipalContext(ContextType.Domain);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.