Role authorization throws "The trust relationship between this workstation and the primary domain failed"

Viewed 839

I'm using ASP.NET Core 3.1 with Windows authentication. If I use [Authorize] alone everything is fine, however, if I decorate my controller with [Authorize(Roles = "GlobalAdmin")] authorisation fails.

This happens in both dev on my local, and after deployment on an intranet.

I definitely have the right roles assigned, because the following works fine:

if(await _userManager.IsInRoleAsync(await _userManager.FindByNameAsync(User.Identity.Name), "GlobalAdmin"))
{
    return View();
}

But I don't want to have to do this on every method in my controller, and I can't use it in a constructor on a base controller because User.Identity returns null in a constructor.

And using the following, var y shows me my correct roles

var x = await _userManager.FindByNameAsync(User.Identity.Name);
var y = await _userManager.GetRolesAsync(x);

In the past I was able to create a custom override on the Authorize attribute and I did my own role validation in there, but core 3.1 seems to have changed methods so they aren't there to override.

But why doesn't the [Authorize] attribute work for my roles in the first place?

In my startup I have

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<IdentityUser, IdentityRole>()
        .AddRoleManager<RoleManager<IdentityRole>>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.Configure<IdentityOptions>(options =>
    {
        options.User.AllowedUserNameCharacters =
            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.\\";
        options.User.RequireUniqueEmail = true;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("MyConnString")));

    services.AddControllersWithViews();
 }

and

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
        if (env.IsDevelopment())
        {
            //app.UseMiddleware<Helpers.ErrorHandlingMiddleware>();
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseMiddleware<Helpers.ErrorHandlingMiddleware>();
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();

        app.UseStaticFiles();

        app.UseAuthentication();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

The entire stacktrace looks like this:

Win32Exception: The trust relationship between this workstation and the primary domain failed.

System.Security.Principal.NTAccount.TranslateToSids(IdentityReferenceCollection sourceAccounts, out bool someFailed)
System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection sourceAccounts, Type targetType, out bool someFailed)
System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection sourceAccounts, Type targetType, bool forceSuccess)
System.Security.Principal.WindowsPrincipal.IsInRole(string role)
Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement+<>c__DisplayClass4_0.b__0(string r)
System.Linq.Enumerable.Any(IEnumerable source, Func<TSource, bool> predicate)
Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement.HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
Microsoft.AspNetCore.Authorization.AuthorizationHandler.HandleAsync(AuthorizationHandlerContext context)
Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler.HandleAsync(AuthorizationHandlerContext context)
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable requirements)
Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

1 Answers

TL:DR;

The error

The trust relationship between this workstation and the primary domain failed.

System.Security.Principal.NTAccount.TranslateToSids(IdentityReferenceCollection sourceAccounts, out bool someFailed)

was simply down to the group not existing on the local system. Once the group was created in the right place, even if it had no members, I stopped getting this particular error.

For development, I had to create my group on my local system, as clued in by @queue in the comments. But it didn't solve my long-term issue - I wanted to use roles defined in the Database!

Long explanation:

I didn't understand the difference between AspNetIdentity and Authorization / Authentication

I'd created a default Windows Authentication app, and ended up creating AspNetIdentity tables as part of my initial code-first migrations so I would have database visibility of my users and could create some extra tables to relate to them. I thought these were intrinsically linked but Identity and Authorisation can exist without each other completely.

I'd used AspNetIdentity classes UserManager and RoleManager to create users and roles in the database, and userManager.IsInRoleAsync(<username>) would show all correct roles entries.

I wanted to use the [Authorize] attribute but I wanted it to ignore Active Directory - it seems to be a faff to set up both in dev and on my iis server, I'd need to rely on higher powers and bother other people. I just wanted it to look at the database roles.

I tried to use an IClaimsTransformation class with [Authorize(Roles = "")] which converts roles from the database in to a proper claim for use by the Windows Authentication. There are a few examples of this on StackOverflow but I kept getting the same error; Windows [Authorization] will fail immediately before it triggers the ClaimsTransformation if the group you're checking for doesn't exist in the system - even if you're going on to ignore them.

There was no order combo of the services in the Startup that would circumvent this.

I ended up using the same solution I did on previous asp.net core apps but slightly different for 3.1:

Created my own Attribute to replace [Authorize] for the top of my controllers

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class RolesAttribute : TypeFilterAttribute
{
    public RolesAttribute(string roles) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] { roles };
    }
}

And created a simple class that luckily allows me to inject my ApplicationDbContext where I have a method that gets a list of roles names for a given username.

public class ClaimRequirementFilter : IAuthorizationFilter
{

    private readonly string _roles;
    private readonly ApplicationDbContext _context;

    public ClaimRequirementFilter(string roles, ApplicationDbContext context)
    {
        _context = context;
        _roles = roles;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {

        List<string> matchingRoles = _context.CheckRoles(context.HttpContext.User.Identity.Name, roles);
        if(matchingRoles.Count == 0) context.Result = new ForbidResult();

    }
}

Injecting UserManager would possibly work too but with the async methods it'd mess up the OnAuthorization method so I stuck with my app context which has the AspNetIdentity tables readily accessible anyway.

Related