Htttpcontext and claim not accessible in TenantInfoMiddleware - asp.net-core

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();
}
}

Related

.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?}");

ASP.NET Core - can't get simple bearer token auth working

I am building an API with ASP.NET Core 2, and I am trying to get a simple auth example that uses a Bearer token to work.
First, here is my Startup code...
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "mydomain.com",
ValidAudience = "mydomain.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("THESECRETKEY THESECRETKEY THESECRETKEY THESECRETKEY"))
};
});
// goes last
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler();
}
app.UseStatusCodePages();
// goes last
app.UseMvc();
}
}
Then I have an Auth controller, that returns the token...
[Route("api/[controller]")]
public class AuthController : Controller
{
[AllowAnonymous]
[HttpPost("RequestToken")]
public IActionResult RequestToken([FromBody] TokenRequest request)
{
if (request.Username == "theuser" && request.Password == "thepassword")
{
var claims = new[]
{
new Claim(ClaimTypes.Name, request.Username)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("THESECRETKEY THESECRETKEY THESECRETKEY THESECRETKEY"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "mydomain.com",
audience: "mydomain.com",
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
return BadRequest("Could not verify username and password");
}
}
public class TokenRequest
{
public string Username { get; set; }
public string Password { get; set; }
}
So in Postman you can see I get a token back...
And then I try GET from a Values controller...
[Route("api/[controller]")]
[Authorize]
public class ValuesController : Controller
{
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
}
If I add the [Anonymous] attribute, it works fine. However, when it requires authorization, I get a 401...
You haven't referenced the Authentication middleware in your Startup.cs
You can reference it by adding app.UseAuthentication(); preferably just before the app.UseMvc();
Here's how your Startup.cs's Configure method should look like:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler();
}
app.UseStatusCodePages();
app.UseAuthentication();
app.UseMvc();
}
You can read more into middleware here and more into authorization with ASP.NET Core here.
You should also look here for everything else about ASP.NET Core

Validating AntiForgery token globally in ASP.NET Core

I want to validate AntiForgery token in ASP.NET Core application. I know i can individually do that by adding [AutoValidateAntiforgeryToken] or [ValidateAntiforgeryToken] attributes on Action methods as suggested in SO post here
I'm looking for global approach to validate token for all POST methods. So i created a middleware to do so. However i could not find suitable method to validate the token. Like in classic asp.net there is AntiForgery.Validate().
What's the equivalent method in ASP.NET Core
public class ValidateAntiForgeryTokenMiddleware
{
private readonly RequestDelegate _next;
public ValidateAntiForgeryTokenMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Method.ToUpper() == "POST")
{
// where does this mehod exists?
// i could not find it in Microsoft.AspNetCore.Antiforgery namespace
AntiForgery.Validate();
}
await _next(httpContext);
}
}
public static class ValidateAntiForgeryTokenMiddlewareExtensions
{
public static IApplicationBuilder UseValidateAntiForgeryToken(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
}
}
I have to Inject Antiforgery as service
public class ValidateAntiForgeryTokenMiddleware
{
private readonly RequestDelegate _next;
private readonly IAntiforgery _antiforgery;
public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
{
_next = next;
_antiforgery = antiforgery;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Method.ToUpper() == "POST")
{
await _antiforgery.ValidateRequestAsync(httpContext);
}
await _next(httpContext);
}
}
add Antiforgery as service in startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery();
}
Use my middlware
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
app.UseValidateAntiForgeryToken();
}

add claims to windows identity

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";
}
}