10

I have configured my web api to work with windows authentication. My goal is essentially to restrict certain actions in my controllers based on a users windows account. Some will be able to preform read actions while others will be able to preform actions that will write to the underlying database. I have found plenty of documentation on how to set up claims based authorization which is the route I think I need to go. What I have not found is how to set this up with windows auth. I think I am missing a middle step such as registering the windows auth as the identity provider?

startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddAuthentication(IISDefaults.AuthenticationScheme);

    services.AddAuthorization(options =>
    {
        options.AddPolicy("readOnly", policy =>
                          policy.RequireClaim(`???????????????????????`));
        options.AddPolicy("write", policy =>
                          policy.RequireClaim(`???????????????????????`));
    });
}

Controller

[Authorize(Policy = "ReadOnly")]
public class MyController : Controller
{
    public ActionResult SomeReadOnlyAction()
    {
        //Return data from database
    }

    [Authorize(Policy = "Write")]
    public ActionResult AWriteAction()
    {
        //Create/Update/Delete data from database
    }
}

I guess another way to ask this question is how do you configure or access claims/roles etc... with windows authentication.

Cœur
  • 37,241
  • 25
  • 195
  • 267
LCaraway
  • 1,257
  • 3
  • 20
  • 48
  • Do you have a call to `services.AddAuthentication(...)`? – haldo Nov 28 '18 at 17:06
  • No I do not.... – LCaraway Nov 28 '18 at 17:08
  • 1
    That's what may be missing. See link: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-2.1 – haldo Nov 28 '18 at 17:09
  • You are absolutely right. I have added that in and updated the code above. I guess my next question is how do i access the credentials to then build a policy around it so that i can use specific authorization attributes on my controller actions? – LCaraway Nov 28 '18 at 17:13

1 Answers1

12

That seems you want to use claims-based authorization via policies . After setting windows authentication in your application , you could add custom claim to ClaimsPrincipal ,check user's identity and confirm which permission current user has :

  1. You can add a claims transformation service to your application:

    class ClaimsTransformer : IClaimsTransformation
    {
        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            var id = ((ClaimsIdentity)principal.Identity);
    
            var ci = new ClaimsIdentity(id.Claims, id.AuthenticationType, id.NameClaimType, id.RoleClaimType);
            if (ci.Name.Equals("name"))
            {
                ci.AddClaim(new Claim("permission", "readOnly"));
            }
            else
            {
                ci.AddClaim(new Claim("permission", "write"));
    
            }
    
    
            var cp = new ClaimsPrincipal(ci);
    
            return Task.FromResult(cp);
        }
    }
    
  2. Add to Startup.cs(.net Core 2.0) :

        services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
    
  3. Set your policy :

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Readonly", policy =>
                              policy.RequireClaim("permission", "readOnly"));
    
            options.AddPolicy("Write", policy =>
                            policy.RequireClaim("permission", "write"));
        });
    
  4. Restrict access to a controller or action by requiring this policy:

        [Authorize(Policy = "Write")]
        public IActionResult Contact()
        {
            ViewData["Message"] = "Your contact page.";
    
            return View();
        }
    

If you have already add groups(write,readonly) in your AD and add the related users to group , you can also check the groups :

public static class Security
{
    public static bool IsInGroup(this ClaimsPrincipal User, string GroupName)
    {
        var groups = new List<string>();

        var wi = (WindowsIdentity)User.Identity;
        if (wi.Groups != null)
        {
            foreach (var group in wi.Groups)
            {
                try
                {
                    groups.Add(group.Translate(typeof(NTAccount)).ToString());
                }
                catch (Exception)
                {
                    // ignored
                }
            }
            return groups.Contains(GroupName);
        }
        return false;
    }
}

And use like :

 if (User.IsInGroup("GroupName"))
 {

 }
Nan Yu
  • 26,101
  • 9
  • 68
  • 148
  • This is great! Where does the `if(User.IsInGroup)...` go? Does it get dropped in the `ClaimsTransformer` Class? – LCaraway Dec 05 '18 at 15:09
  • Or would you use that as an alternative to the ClaimsTransformer? – LCaraway Dec 05 '18 at 15:51
  • @LCaraway , it depends you , it can be in any class . – Nan Yu Dec 06 '18 at 01:24
  • When the `group.Translate()` is ran it returns an exception stating the trust relationship between the workstation and the primary domain has failed... – LCaraway Dec 11 '18 at 17:26
  • See how to solve the issue :https://support.microsoft.com/en-hk/help/2771040/the-trust-relationship-between-this-workstation-and-the-primary-domain and https://support.citrix.com/article/CTX227147 – Nan Yu Dec 12 '18 at 01:20
  • Isn't the code faster if you translate the AD-GroupName to the SID representation. `IdentityReference sidAdGroup = new NTAccount(GroupName).Translate(typeof(SecurityIdentifier));` then comparing it with the SIDs in `wi.Groups` (Instead of translate/cast all wi.Groups to NTAccount?) – Stef Chäser Jan 30 '20 at 09:39
  • Why not just use the built in User.IsInRole(AD Group Name) for option 2? – Caesar Tex Jul 29 '21 at 19:34
  • Great answer, really helpful thanks – jondow Mar 17 '22 at 09:06