Claims based authentication, with active directory, without ADFS - authentication

I have a client asking for an integrated authentication based solution utilizing a custom role/membership schema. My original plan was to use claims based authentication mechanism with integrated authentication. However, my initial research is not turning up a whole lot of useful information.
To the point, I have an ASP.NET (not core nor owin) WebAPI application, which has api actions used by angular SPA based (asp.net) web application. I am attempting to authorize the api calls using integrated authentication. My initial effort was focused around a custom AuthorizationAttribute and ClaimsAuthenticationManager implementation. However as I got deeper into that I started running into issues with the custom ClaimsAuthenticationManager, at this point I'm not sure that is the proper route to take.
So my question for you all is, can you at least give me some ideas of what it would take to make this happen? I don't need help with secific bits the code, just need to figure out the appropriate "stack" so to speak.
The only real requirement is WebAPI calls can be authorized, with a custom attribute passing a name of a claim to authorize on, but the claim is not in AD even though it is using windows authentication, the claims themselves would come from a database.
Thank you all in advance!

Look at https://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api.
Your scenario isn't much different:
you're using AD for authentication
you're using your db for authorization
Simply put this can be addressed by configuring web-api to use windows authentication.
<system.web>
<authentication mode="Windows" />
</system.web>
And add your own IAuthorizationFilter to Web API pipeline, that will check current principal (should be set), and then override this principal with your own (i.e. query db - get claims, and override it with your custom claims principal by setting HttpContext.Current.User and Thread.CurrentPrincipal).
For how to add filter to WebAPI pipe line check out How to add global ASP.Net Web Api Filters?
public class CustomAuthenticationFilter : IAuthenticationFilter {
public bool AllowMultiple { get { return true; } }
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken) {
var windowsPrincipal = context.Principal as WindowsPrincipal;
if (windowsPrincipal != null) {
var name = windowsPrincipal.Identity.Name;
// TODO: fetch claims from db (i guess based on name)
var identity = new ClaimsIdentity(windowsPrincipal.Identity);
identity.AddClaim(new Claim("db-crazy-claim", "db-value"));
var claimsPrincipal = new ClaimsPrincipal(identity);
// here is the punchline - we're replacing original windows principal
// with our own claims principal
context.Principal = claimsPrincipal;
}
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken) {
return Task.FromResult(0);
}
}
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.Filters.Add(new CustomAuthenticationFilter());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute( ... );
}
}
Also there is no need for custom authorization attribute - use default one - its understood by everyone, and makes your code more readable.

Related

.Net 6: Enable Windows and Anonymous authentication for one

I work on a .Net core application and I need to mix windows and anonymous authentication within the same endpoint(s). So the goal is to be able to determine the windows user but the endpoint should also work when no windows user is present (aka windows authentication fails).
My problem is that when I use the Authorize attribe (as shown in the example below), the endpoint will only be called when windows authentication succeded. If I additionaly add the [AllowAnonymous] attribute, the User is never authenticated.
Example: (
[Authorize(AuthenticationSchemes = IISDefaults.AuthenticationScheme)]
public IActionResult Index()
{
_log.LogDebug("IsAuthenticated = " + this.User.Identity.IsAuthenticated.ToString());
_log.LogDebug("Authenticated Name: " + this.User.Identity.IsAuthenticated.Name);
return View();
}
How can this be done in .Net 6.0? It should be really simple as authentication and authorization should be separated but it seems they are quite intertwined. I haven't found a solution after extensive googling, checking the .net core source code and trying out myself.
Is there a good way to solve this?
Remark 1: there are solutions for .Net core 3.1 but then don't work in .Net 6 Enable both Windows authentication and Anonymous authentication in an ASP.NET Core app
Remark 2: we have endpoint that have to work with Windows Authentication only and other with anonyomous authentication. These both work fine within the same application. It is really about being able to detect the windows user in an endpoint that otherwise supports anymous authentication.
I (or better we) have found a solution that works even when Windows authentication is disabled on IIS. It is not very elegant but this is what we came up with. The idea is basically to trigger another call to an endpoint to determine if the user is actually a windows loging or not. If this call is successful, then we know we have a windows user and can act accordingly, for example do a redirect to an endpoint that requires windows authentication.
Remark: If you can control the IIS settings - which probably is often the case - , then I suggest you go with the solution proposed here:
enable-both-windows-authentication-and-anonymous-authentication-in-an-asp-net-co )
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> TestWindowsAuthAsync(CancellationToken cancellationToken)
{
using var client = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
var response = await client.GetAsync($"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.PathBase}{HttpContext.Request.Path}/HasUserWindowsAuth");
if (response.IsSuccessStatusCode)
{
// Yes, now we know that user indeed has windows authentication and we can act upon it
return RedirectToAction("QuickLogin", input);
}
// No windows credentials have been passed at this point
return View();
}
[HttpGet("HasUserWindowsAuth")]
[Authorize(AuthenticationSchemes = IISDefaults.AuthenticationScheme)]
public IActionResult HasUserWindowsAuth() => Ok();
[HttpGet("QuickLogin")]
[Authorize(AuthenticationSchemes = IISDefaults.AuthenticationScheme)]
public async Task<IActionResult> QuickLoginAsync(LoginModel input, CancellationToken cancellationToken)
{
var user = this.User.Identities.FirstOrDefault(i => i System.Security.Principal.WindowsIdentity && i.IsAuthenticatd);
// do something with that user
}

.Net Core 3.1 ClaimsTransformation Manually Added Claims Not Persisting

I will be accessing several tables to determine if a user is "Validated" or not as well as adding custom roles to a Windows authenticated user for authorization. For now I'm running a test in a basic .net Core web application just to see how I should be doing this. I have setup a RequiredClaim in my Fallback Policy and a ClaimsLoader and it works great:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddTransient<IClaimsTransformation, ClaimsLoader>();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireClaim("ValidatedUser")
.Build();
});
}
public class ClaimsLoader : IClaimsTransformation
{
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = (ClaimsIdentity)principal.Identity;
claimsIdentity.AddClaim(new Claim("ValidatedUser", ""));
return await Task.FromResult(principal);
}
}
As long as that AddClaim line is in there, they can access the app, without it they get a not-authorized response which is what I want.
Based on what I've read I thought any claims/roles I add in the transformation should come back each time but they do not. In the code above I have the AddClaim running every time so it's working, but in reality I will be going to a database to determine if I should add that claim which is an expensive process. I want to persist the results across multiple requests. So I want to check if the claim is already there and not bother getting it again if it is. For whatever reason it is NEVER there when it comes back for a second request.
From what I've read here back in 2.x the claims should persist:
https://philipm.at/2018/aspnetcore_claims_with_windowsauthentication.html
But here in my 3.1 application they do not.

How can I use Asp.Net Core 2.0's in-memory TestServer class for Integration Tests when my API requires an Authorization Token?

I am working on an ASP.NET Core 2.0 Web API and I want to do some integration tests using ASP.NET Core's TestServer class. I am using xUnit as my testing framework so I have created a TestServerFixture class that creates the in-memory TestServer instance and then use the TestServer's .CreateClient() to create the HTTPClient instance.
My Web API requires an OAuth2.0 Access Token from my Azure AD. I set this up using this code in my Startup.cs, ConfigureServices method:
// Add Azure AD OAUTH2.0 Authentication Services
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
and in my controllers, I have the [Authorize] attribute on the class.
So for my Integration Tests setup, I have a method in my TestServerFixture that obtains a valid token from Azure AD and I add it to my client request header as follows;
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _testServerFixture.GetAccessToken());
When I debug my integration test, I can see that the request does contain a valid access token but I am still getting a 401 Unauthorized from the API when I run my Integration Test.
After doing some digging I found several resources that talk about a similar issue with TestServer, but related to Authentication rather than Authorization, as I am experiencing. Here are links to these resources;
https://medium.com/#zbartl/authentication-and-asp-net-core-integration-testing-using-testserver-15d47b03045a
How do I integration test a ASP 5/Core Web API with [Authorize] Attributes
http://geeklearning.io/how-to-deal-with-identity-when-testing-an-asp-net-core-application/
These all talk about assigning a ClaimsPrincipal to the context.user using custom middleware. Since this is based upon Authentication rather than Authorization, I am not sure if I can do something similar for my Access Token.
I do know that in my API, I can access the HTTPContext.User and pull out the AppId value, which is part of the Access Token so it would seem that Authentication and Authorization both use the Context.User.
So, before I burn time building up my own custom middleware for this purpose, I wanted to see if anyone has already addressed this issue or perhaps are aware of a NuGet that does what I need.
EDIT - SOLUTION
I am showing this in case anyone else runs into this issue.
I ended up building the middleware that Zach Bartlett presented in his blog , but making the following changes.
public class AuthenticatedTestRequestMiddleware
{
#region Class Variables
private const string TestingAccessTokenAuthentication = "TestingAccessTokenAuthentication";
private readonly RequestDelegate _next;
#endregion Class Variables
#region Constructor(s)
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
#endregion Constructor(s)
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Integration-Testing"))
{
if (context.Request.Headers.Keys.Contains("Authorization"))
{
var token = context.Request.Headers["Authorization"].First();
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Authentication, token)
}, TestingAccessTokenAuthentication);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
context.User = claimsPrincipal;
}
}
await _next(context);
}
}
There were one interesting "Gotcha".
In Zach's blog he had the code;
public const string TestingHeader = "X-Integration-Testing";
at the top of his middleware and then references the TestingHeader in the test for the key in the header collection like this;
if (context.Request.Headers.Keys.Contains(TestingHeader)
Doing it this way was failing for me until I put the string literal instead of the variable into the .Contains() clause.
Now, my integration test is passing with a 200 OK response. :)
I was able to find a solution following Zach Bartlett's blog post, and making some small changes to make it pertain to the Authentication header. The code is shown as an edit in my original post above.

ASP Core Add Custom Claim to Auth Token

I'm using openiddict and ASP Identity, and I am trying add a "GroupId" as a claim to the auth token that is returned when logging in (using the /connect/token endpoint - see example I followed below). The GroupId is a property in my AplicationUser class.
I have tried using an IClaimsTransformer but that seems clunky, I can't easily get to the UserManager from the ClaimsTransformationContext.
How would I go about either getting the UserManager through DI in my IClaimsTransformer or just adding the GroupId to the token that is generated at the connect/token endpoint?
I followed this example for setting up my site. This is what I would like to do:
var groupGuid = User.Claims.FirstOrDefault(c => c.Type == "GroupGuid");
There is a couple of ways to achieve it:
First, override CreateUserPrincipalAsync method in your custom SignInManager:
public override async Task<ClaimsPrincipal> CreateUserPrincipalAsync(ApplicationAdmin user)
{
var principal = await base.CreateUserPrincipalAsync(user);
// use this.UserManager if needed
var identity = (ClaimsIdentity)principal.Identity;
identity.AddClaim(new Claim("MyClaimType", "MyClaimValue"));
return principal;
}
The second way is to override CreateAsync method of your custom UserClaimsPrincipalFactory:
public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
var identity = (ClaimsIdentity)principal.Identity;
identity.AddClaim(new Claim("MyClaimType", "MyClaimValue"));
return principal;
}
which is, basically, the same, because base.CreateUserPrincipalAsync method in SignInManager calls this.UserClaimsPrincipalFactory() inside.
Don't forget to add your custom implementations into services:
either
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSignInManager<CustomSignInManager>();
}
or
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomClaimsPrincipalFactory>();
}
ASP Core Add Custom Claim to Auth Token
You can't change a token after it is signed by IdP, so you can't add claim to token.
I have tried using an IClaimsTransformer but that seems clunky, I
can't easily get to the UserManager from the
ClaimsTransformationContext.
I guess your problem is related this github issue. In summary(as far as i understand) if ClaimsTransformer class is registered as singletion and one of the its dependency is scoped or transient, it causes captive dependency. In this case you should use Service Locator pattern to avoid from captive dependency. Your code may be something like this(from #PinpointTownes' comment):
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
var userManager= context.Context.RequestServices.GetRequiredService<UserManager>();
//
}
-- My thoughts about your case --
You have basically two options to achieve your goal:
Add claim to token when token is generated by IdP:
You don't need this method most cases, but if you want to use it:
You should have control over IdP, because this option is possible on the IdP(as far as i understand your IdP and Resource Server is same, so you have control over IdP, but it might not be possible always) .
You should take care of inconsistency when using this option, because the claim is stored in the token and doesn't get each request. So the real value of claim might be different from claim in the token.(I don't prefer it for roles, permissions, groups etc. because these claims can be change anytime).
p.s: i don't know if it is possible to add claims to token with Openiddict.
Claims Transformation
Actually i used HttpContext.Items to store additional claims before i discovered this method and it worked well for me. But i think better way is to use Claims Transformation and it fits into your case.

ASP.NET MVC 4 Read Persistent Auth Cookie Event?

I'm using WebSecurity for Authentication and a custom ClaimsAuthorizationManager for Authorization.
Mixing Claims based authorization with the built in WebSecurity features for Authentication has provided me with a ton of value. I highly recommend it for anyone who requires complex authorization logic combining from several systems.
Anyways, everything is working great except the RememberMe feature.
When a user logs in, I set my auth cookie (via WebSecurity), new up my ClaimsPrincipal, and write it to my SessionSecurityToken. Bam, it works brilliantly.
However, when a user has previously elected to persist the (Websecurity) auth cookie, she is allowed to bypass my login method, which news up my ClaimsPrincipal and writes my principal to my SessionSecurityToken. My authorization fails because my claims haven't been loaded, because I haven't had a chance to transform my ClaimsPrincipal.
Is there a way to hook into a (Websecurity) "forms authentication cookie read" event? If so, I could handle it, new up my ClaimsPrincipal, and be on my way. Thanks in advance!
You could write a custom AuthorizeAttribute:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (authorized)
{
httpContext.User = new ClaimsPrincipal(...)
}
return authorized;
}
}
Now decorate your protected controller actions with this custom attribute instead of the default built-in:
[MyAUthorize]
public ActionResult Protected()
{
...
}