add claims to windows identity - authentication

I am trying to assign roles as claims for Windows Authentication for Asp.net Core Webapi project. Below is my transform by adding a role claim current identity.
public class ClaimsTransformer : IClaimsTransformer
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
//add new claim
var ci = (ClaimsIdentity) context.Principal.Identity;
var c = new Claim(ClaimTypes.Role, "admin");
ci.AddClaim(c);
return Task.FromResult(context.Principal);
}
}
And this middleware is added to Startup.Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
loggerFactory.AddDebug();
app.UseClaimsTransformation(o => new ClaimsTransformer().TransformAsync(o));
app.UseStaticFiles();
app.UseMvc();
}
However role admin is not authorized in this method (403-Forbidden).
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values/5
[HttpGet("{id}")]
[Authorize(Roles = "admin")]
public string Get(int id)
{
return "value";
}
}
It is working properly if [Authorize] is used. Any missing?

Unfortunately User.IsInRole method doesn't work with ClaimsTransformer(if you add role with ClaimsTransformer, IsInRole will be false) so you can't use [Authorize(Roles = "")] with ClaimsTransformer. In this case you can use Claims Based Authorization to handle authotorization.
So add below code to ConfigureServices and use Authorize attribute:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddAuthorization(options =>
{
options.AddPolicy("admin", policy => policy.RequireClaim(ClaimTypes.Role, "admin"));
});
//...
}
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values/5
[HttpGet("{id}")]
[Authorize(Policy = "admin")]
public string Get(int id)
{
return "value";
}
}

Related

Htttpcontext and claim not accessible in TenantInfoMiddleware

I have a employee controller I have added two lines to get the employee TenantName one using httpcontext and a other using user claim. I want to get the TenantName in TenantInfoMiddleware
[HttpPost, Route("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginModel user)
{
Claim tenantName = new Claim("TenantName", "erp_colombia");
HttpContext.Items["TenantName"] = "erp_colombia";
}
However in my TenantInfoMiddleware both testHttpContext and testFromClaim are null why is that?
public class TenantInfoMiddleware
{
private readonly RequestDelegate _next;
public TenantInfoMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
List<Claim> claims = new List<Claim>();
claims = context.User.Claims.ToList();
var testHttpContext = context.Items["TenantName"];
Claim claim = claims.Where(x => x.Type == "TenantName").FirstOrDefault();
var testFromClaim = claim.Value;
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
I have added the TenantInfoMiddleware in startup
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
[Obsolete]
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<TenantInfoMiddleware>();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
}
}

.net core 5.0.2 and jwt => response 401 Unauthorized

I am following an video tutorial for identity server 4 with web api's.
And Im not sure when I went wrong.
Im getting 401 Unauthorized when I try to call api with bearer token.
In previos step, without authorization, my api worked.
This is my api controller in my TablesReach.API project:
...
namespace TablesReach.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly DataContext _context;
public UsersController(DataContext context)
{
_context = context;
}
// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
...
this is my Startup.cs of my api project:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(opts =>
{
opts.Authority = "http://localhost:5000";
opts.RequireHttpsMetadata = false;
opts.ApiName = "TablesReachApi";
});
services.AddDbContext<DataContext>(opts => opts.UseInMemoryDatabase("UNWDb"));
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseAuthentication();
}
}
My other project TablesReach.IdentityServer is host on localhost:5000
and Im being able to get bearer token, so I assume that this project is quite OK.
identityServer startup.cs class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiScopes(Config.GetAllApiResources())
.AddInMemoryClients(Config.GetClients());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//else
//{
// app.UseExceptionHandler("/Home/Error");
// // 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.UseRouting();
//app.UseAuthorization();
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllerRoute(
// name: "default",
// pattern: "{controller=Home}/{action=Index}/{id?}");
//});
app.UseIdentityServer();
}
}
and Config.cs:
public class Config
{
public static IEnumerable<ApiScope> GetAllApiResources()
{
return new List<ApiScope>
{
new ApiScope("TablesReachApi", "Api for solution")
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "TablesReachApi" }
}
};
}
}
Note: When I remove annotation [Authorize] from my api controller I can reach my method.
For some middleware, order matters. Authentication and authorization, for example, can't go in the order that you have put them in the API. Microsoft has some clear documentation on this for you to read here..

error 404 showing for each controller after user authorization via ldap

Scenario:
I'm implementing asp.net core 3.1 MVC project. I authorize my user via ldap Active Directory service. The user authenticates successfully and enter into my website. but after clicking on each menu item in order to see the related controller index it shows white page. I wrote on top of all my controller class [Authorize] keyword in order to let any authorized user to see all controllers.
My Problem is:
when user clicks on each menu item in home in order to see the related controller's index, it shows white page and when I publish my project on ldap server, it shows me 404 error. I appreciate if any one can suggest me a solution. It seems to the routing has problem but I'm not sure. I even wrote on top of my controller class the keyword [AllowAnonymous] but still I see white pages for index pages for each controller. Should I add anything to startup.cs for AutheticationHelper or CustomAuthenticationMiddleware as a service?
Here is my sign in method in account controller
namespace CSDDashboard.Controllers
{
[Route("[controller]/[action]")]
[AllowAnonymous]
public class AccountController : Controller
{
private readonly LdapUserManager _userManager;
private readonly LdapSignInManager _signInManager;
private readonly ILogger _logger;
public AccountController(
LdapUserManager userManager,
LdapSignInManager signInManager,
ILogger<AccountController> logger)
{
this._userManager = userManager;
this._signInManager = signInManager;
this._logger = logger;
}
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> Signin(string returnUrl = null)
{
// Clear the existing external cookie to ensure a clean login process
await this.HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
this.ViewData["ReturnUrl"] = returnUrl;
return this.View();
}
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Signin(SigninViewModel model, string returnUrl = null)
{
this.ViewData["ReturnUrl"] = returnUrl;
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "tehran.iri"))
{
// validate the user's credentials
//var result = ctx.ValidateCredentials(model.UserName, model.Password);
// try {
if (ctx.ValidateCredentials(model.UserName, model.Password))
{
// credentials are OK --> allow user in
HttpContext.Session.MarkAsAuthenticated(model.UserName);
//Added recently
Debug.Writeline(string.Format("Redirection to {0}", returnUrl);
return RedirectToLocal(returnUrl);
}
else
{
this.TempData["ErrorMessage"] = "The username and/or password are incorrect!";
return this.View(model);
// credentials aren't OK --> send back error message
}
}
}}}
Here is my middleware class and AuthenticationHelper class
public static class AuthenticationHelper
{
private const string SessionKey = "AuthenticationHelper.UserName";
public static void MarkAsAuthenticated(this Microsoft.AspNetCore.Http.ISession session, string authenticatedUserName)
{
session.SetString(SessionKey, authenticatedUserName);
}
public static ClaimsPrincipal GetAuthenticatedUser(this Microsoft.AspNetCore.Http.ISession session)
{
string authenticatedUserName = session.GetString(SessionKey);
if (string.IsNullOrEmpty(authenticatedUserName)) return null;
return new GenericPrincipal(new GenericIdentity(authenticatedUserName), Array.Empty<string>());
}
}
public class CustomAuthenticationMiddleware
{
private readonly RequestDelegate _next;
public CustomAuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
ClaimsPrincipal user = context.Session.GetAuthenticatedUser();
if (user != null) context.User = user;
await _next(context);
}
}
public static class CustomAuthenticationMiddlewareExtensions
{
public static IApplicationBuilder UseCustomAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomAuthenticationMiddleware>();
}
}
Here is my code in statrup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<LdapSettings>(Configuration.GetSection("LdapSettings"));
services.AddDbContext<LdapDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("CSDDashboardContext")));
//-------------------------------------------------
services.AddIdentity<LdapUser, IdentityRole>()
.AddEntityFrameworkStores<LdapDbContext>()
.AddUserManager<LdapUserManager>()
.AddSignInManager<LdapSignInManager>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "CSDDashboard";
options.LoginPath = "/Account/Signin"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Account/Signout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
});
services.AddRazorPages();
services.AddTransient<ILdapService, LdapService>();
//-------------------------------------------------
services.AddControllersWithViews();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);//We set Time here
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddDistributedMemoryCache();
//Notice this is NOT the same class... Assuming this is a valid DBContext. You need to add this class as well.
services.AddDbContext<CSSDDashboardContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CSDDashboardContext")));
services.AddDbContext<CSDDashboardContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CSDDashboardContext")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// app.UseDeveloperExceptionPage(options);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// 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.UseSession();
app.UseRouting();
app.UseCustomAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
//Here are all of my controllers, but as it seems just I can uncomment one controller pattern here, I commented all the others
// pattern: "{controller=Applications}/{action=Index}/{id?}");
//pattern: "{controller=Home}/{action=Index}/{id?}");
// pattern: "{controller=ApiApplications}/{action=Index}/{id?}");
pattern: "{controller=Gates}/{action=Index}/{id?}");

Active Directory based custom authorization

I have an ASP.NET Core application that needs to decide whether or not to return information from controller end points based on the requesting client's Active Directory groups. I tried to implement custom authorization as follows:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.AddSingleton(Configuration);
services.AddSignalR();
services.AddAuthorization(options=> {
options.AddPolicy("RequiredADGroupName", policy=>policy.Requirements.Add(new ActiveDirectoryRequirement("RequiredADGroupName")));
});
}
ActiveDirectoryRequirement.cs
public class ActiveDirectoryRequirement: IAuthorizationRequirement
{
public string ActiveDirectoryGroupName { get; set; }
public ActiveDirectoryRequirement(string groupName)
{
ActiveDirectoryGroupName = groupName;
}
}
ActiveDirectoryHandler.cs
public class ActiveDirectoryHandler: AuthorizationHandler<ActiveDirectoryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ActiveDirectoryRequirement requirement)
{
//authorization logic
}
}
this next one is just an example of how i would use this authorization.
HomeController.cs
public class HomeController : Controller
{
[Authorize(Policy = "RequiredADGroupName")]
public IActionResult Index()
{
return View();
}
}
When i run the project, i get the following error:
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultForbidScheme found.
what am i doing wrong?
Try to register IAuthorizationHandler with ActiveDirectoryHandler like:
services.AddAuthorization(options => {
options.AddPolicy("RequiredADGroupName", policy =>
{
policy.Requirements.Add(new ActiveDirectoryRequirement("RequiredADGroupName"));
});
});
services.AddSingleton<IAuthorizationHandler, ActiveDirectoryHandler>();

Middleware check the annotation from controller method

Hello i want to check the annotation from a controller method in a middleware class.
My config:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, BackendDbContext context)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseStaticFiles();
app.UseMiddleware<AuthMiddleware>();
app.UseMvc();
BackendDbInitializer.Init(context);
}
My Controller:
Route("api/[controller]")]
public class UserController : Controller
{
private readonly BackendDbContext _context;
public UserController(BackendDbContext context)
{
_context = context;
}
// GET api/values
[HttpGet]
[NoAuth]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
My Middleware:
public class AuthMiddleware
{
private readonly RequestDelegate _next;
public AuthMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//Here i want to check if the called method in UserController have a annotation...
await _next.Invoke(context);
}
}
In the AuthMiddleware i want to check if the called method have a specific annotation.
I don't know if this question is outdated, I came across the same question today, I'm using Casbin.Net package to implement RBAC for my asp dotnet core project, I need to implement an auth middleware which can recognize controllers with a [Authorize] annotation, so only these controllers need to check permission, the other controllers not, below are my code
rbac with asp dotnet core using casbin
You can ignore the dependency inject and casbin parts, in AuthzMiddleware.cs, I use context.User.Claims.Count() > 0 to check if the current request has passed a Authentication middleware.