I have MiniProfiler set up in an ASP.NET Core application. Profiling works fine.
However, I only want Admins to be able to profile.
I have the following in ConfigureServices:
services.AddMiniProfiler(options =>
{
options.ShouldProfile = request =>
request.HttpContext.User.IsInRole("Admin");
});
The problem is, the user identity does not seem to be loaded in that method.
The User.Identity.Name property is null, and there are no claims.
My guess is that this call happens before that info is populated?
How can I profile based on the user identity?
You need to know that according to the docs the ClaimsPrincipal.IsInRole() method checks for Claims of type ClaimsIdentity.RoleClaimType.Be sure you have added the role claims.
Here is a working demo you could follow:
1.Register the user with name a#qq.com successfully.
2.Generate the role and add the role with claims to the user:
public async Task CreateRolesandUsers()
{
bool x = await _roleManager.RoleExistsAsync("Admin");
if (!x)
{
// first we create Admin role
var role = new IdentityRole();
role.Name = "Admin";
await _roleManager.CreateAsync(role);
//must add the claim,otherwise IsInRole would always be false..
_roleManager.AddClaimAsync(role, new Claim(ClaimTypes.AuthorizationDecision, "Admin")).Wait();
}
var user = _userManager.FindByNameAsync(User.Identity.Name).Result;
if (user != null)
{
var result1 = await _userManager.AddToRoleAsync(user, "Admin");
}
}
2.Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultUI();
services.AddMiniProfiler(options =>
{
options.RouteBasePath = "/profiler";
options.ShouldProfile = request =>
request.HttpContext.User.IsInRole("Admin");
options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
});
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication(); //be sure add this
app.UseAuthorization();
app.UseMiniProfiler(); //add this before UseEndpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
Result:
Related
I am trying to implement SSO Authentication in ASP.Net Core 3.1 and deploy in Pivot Cloud Foundry(PCF).
In local it's working fine but after deployment getting below error
An unhandled exception occurred while processing the request.
Exception: The oauth state was missing or invalid.
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
HomeController.cs
#if !LOCALTEST
[Authorize]
#endif
public IActionResult Index()
{
string user = "";
if (User.Identity.IsAuthenticated)
{
user = User.Identity.Name;
}
else
{
// WindowsIdentity.GetCurrent
user = WindowsIdentity.GetCurrent().Name.Substring(WindowsIdentity.GetCurrent().Name.LastIndexOf(#"\") + 1);
}
TempData["user"] = user;
return View();
}
Manifest.yml
---
applications:
- name: ApplicationName
memory: 1G
stack: cflinuxfs3
buildpacks:
- dicf_dotnet_core_buildpack_2339_cflinuxfs3
instances: 2
disk_quota: 1G
env:
ASPNETCORE_ENVIRONMENT: Development
GRANT_TYPE: authorization_code
SSO_IDENTITY_PROVIDERS : XXX-sso
SSO_SCOPES : openid,roles,user_attributes
SSO_AUTO_APPROVED_SCOPES : openid,roles,user_attributes
SSO_USERINFO_URL : https://appsso.login.sr3.pcf.xxx.com/userinfo
services :
- serviceName
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc();
services.Configure<MyOptions>(myOptions =>
{
myOptions.ConnString = Configuration.GetConnectionString("DefaultConnection");
});
services
.AddMvc()
.AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
services.AddScoped<IRepository, RepositoryConcrete>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddCloudFoundryContainerIdentity(Configuration);
services.AddAuthentication((options) =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme;
})
.AddCookie((options) =>
{
options.AccessDeniedPath = new PathString("/Home/AccessDenied");
})
.AddCloudFoundryOAuth(Configuration);
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddControllersWithViews();
var serviceInfos = CloudFoundryServiceInfoCreator.Instance(Configuration);
var ssoInfo = serviceInfos.GetServiceInfos<SsoServiceInfo>().FirstOrDefault()
?? throw new NullReferenceException("Service info for an SSO Provider was not found!");
userInfoEndPoint = ssoInfo.AuthDomain + "/userinfo";
}
// 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.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto
});
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Please help me to know what i am missing or required modification.
Thanking in Advance!
I have a dotnet 5 web API with an Angular2+ front end, which I'm building from a previous 3.1 MVC version of the app. I'm having an issue with CORS while looking to authenticate with Yahoo that I did not have with the MVC version. The error I am getting is:
"Access to XMLHttpRequest at 'https://api.login.yahoo.com...' (redirected from 'https://localhost:5003/api/draft/yahooauth/') from origin 'https://localhost:5003' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."
My API Controller:
[EnableCors("MyPolicy")]
[HttpPost("yahooauth")]
public void YahooAuth()
{
string leftUrl = string.Format("{0}://{1}", HttpContext.Request.Scheme, HttpContext.Request.Host);
string returnUrl = $"{leftUrl}/api/draft/yahooexchange";
string id = configuration["YahooClientId"];
string secret = configuration["YahooClientSecret"];
string url = $"https://api.login.yahoo.com/oauth2/request_auth?client_id={id}&redirect_uri={returnUrl}&response_type=code&language=en-us";
Response.Redirect(url);
}
[HttpGet("yahooexchange/{code}")]
public IActionResult yahooexchange(string code)
{
// Code that is supposed to be triggered with returnUrl from Yahoo
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration) => Configuration = configuration;
public IConfiguration Configuration { get; }
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential
// cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
// requires using Microsoft.AspNetCore.Http;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<IdentityDbCxt>(options => options.UseSqlServer(Configuration.GetConnectionString("IdentityDb")));
services.AddDbContext<DraftContext>(options => options.UseSqlServer(Configuration.GetConnectionString("FSDraftDb")));
services.AddIdentity<AppUser, IdentityRole>(opts =>
{
opts.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<IdentityDbCxt>()
.AddDefaultTokenProviders();
services.AddTransient<IEmailSender, EmailSender>();
services.AddTransient<IAPICall, APICall>();
services.AddTransient<IDraftService, DraftService>();
services.AddCors(options =>
{
options.AddPolicy("MyPolicy",
builder =>
{
builder.WithOrigins("*")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddMvc()
.AddRazorRuntimeCompilation();
services.AddMemoryCache();
services.AddSession();
services.AddSingleton<IHtmlSanitizer, HtmlSanitizer>();
services.AddControllersWithViews(/*options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())*/);
services.AddRazorPages();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1",
new OpenApiInfo { Title = "API", Version = "v1" });
});
services
.AddControllers()
.AddNewtonsoftJson();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostEnvironment env, IServiceProvider services)
{
//app.UseMiddleware<GCMiddleware>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStatusCodePages();
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseSession();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}");
endpoints.MapControllerRoute(
name: "angular_fallback",
pattern: "{target:regex(draft|data|keeper|adminapi|admin`):nonfile}/{*catchall}",
defaults: new { controller = "Home", action = "Index" });
endpoints.MapRazorPages();
});
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
app.UseSpa(spa =>
{
string strategy = Configuration.GetValue<string>("DevTools:ConnectionStrategy");
if (strategy == "proxy")
{
spa.UseProxyToSpaDevelopmentServer("http://127.0.0.1:4200");
}
else if (strategy == "managed")
{
spa.Options.SourcePath = "../ClientApp";
spa.UseAngularCliServer("start");
}
});
}
}
I've looked at a few sources, including one quite hopeful post here, but this did not solve my problem: Problems with CORS Response to preflight in dotnet core 3.1
In case it's helpful, I'm looking to do step 2: https://developer.yahoo.com/oauth2/guide/flows_authcode/
Edit: This is the documentation I used regarding CORS: https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-5.0#attr
Try to use this syntax and move AddCors to the top of ConfigureServices. Assign name to UseRouting.
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
.......
....
app.UseRouting();
app.UseCors("MyPolicy");
app.UseAuthentication();
app.UseAuthorization();
.....
So I ended up figuring out a workaround and just created a new MVC controller, as my previous MVC app worked fine.
In addition, I realized that I had not updated my Yahoo Developer App for the new return URI. This did not fix my CORS problem, but it was preventing this from properly working.
Code from Angular to go to the new controller.
window.location.href = "https://localhost:5003/yahoo/index";
My MVC Controller:
public class YahooController : Controller
{
private IAPICall apiCall;
public YahooController(IAPICall aCall)
{
apiCall = aCall;
}
// getting session data on player values
private DraftService GetDS()
{
DraftService draftService = HttpContext.Session.GetJson<DraftService>("DraftService") ?? new DraftService();
return draftService;
}
// saving session data on player values
private void SaveDS(DraftService ds)
{
HttpContext.Session.SetJson("DraftService", ds);
}
[HttpGet]
public void Index()
{
string returnUrl = Url.Action("yahooexchange",
"yahoo", new
{
},
protocol: HttpContext.Request.Scheme);
string url = apiCall.YahooAuthUrl(returnUrl);
Response.Redirect(url);
}
public void yahooexchange(string code)
{
string content = apiCall.YahooGetToken(code);
var jContent = JsonConvert.DeserializeObject<JObject>(content);
string accessToken = jContent.GetValue("access_token").ToString();
string refreshToken = jContent.GetValue("refresh_token").ToString();
string leftUrl = string.Format("{0}://{1}", HttpContext.Request.Scheme, HttpContext.Request.Host);
DraftService ds = GetDS();
ds.YahooAccessToken = accessToken;
ds.YahooRefreshToken = refreshToken;
SaveDS(ds);
string url = leftUrl + "/draft/" + ds.DraftId;
Response.Redirect(url);
}
}
When I successfully signed in to the HTTP context and I'm redirected to the home controller, the Authorize Attribute of the HomeController redirects me to the login path, because I don't know. (Infinite loop)
The validate async method doesn't sign me out or reject the cookie (I checked it.)
But where's the problem?
I have the same authentication process in an ASP.NET Core 2.1 project and there it works perfectly.
I test it with a custom authorize attribute to check the context. In the context I've a principal and I'm authenticated.
But why redirect me the standard authorize attribute?
My configuration is:
HomeController.cs
public class HomeController : Controller
{
[Authorize]
public IActionResult Index()
{
return this.View("Index");
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/auth/login/";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.Events.OnValidatePrincipal = ValidateAsync;
});
services.AddControllersWithViews();
services.AddAntiforgery();
services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
});
}
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.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
context = context ?? throw new ArgumentNullException(nameof(context));
String userId = context.Principal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;
if(userId == null)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
ApplicationDbContext dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
User user = await dbContext.Users.FindAsync(Guid.Parse(userId));
if(user == null)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
if(!user.StaySignedIn &&
user.LastLogin != null &&
(user.LastLogin.Subtract(TimeSpan.FromDays(1)) > DateTimeOffset.Now))
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
}
AuthController.cs
[Route("/login")]
[Route("/auth/login")]
public async Task<IActionResult> Login([FromForm]LoginModel loginModel)
{
Claim nameIdentifier = new Claim(ClaimTypes.NameIdentifier, user.Id.ToString());
ClaimsIdentity userIdentity = new ClaimsIdentity(new List<Claim> { nameIdentifier }, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(userIdentity);
await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
return this.RedirectToAction("Index", "Home");
}
I found the solution for my problem.
The order of
app.UseAuthorization();
app.UseAuthentication();
Must be changed and then it works.
The issue closed When using identity is not allowed to use the cookies.
It overrides some of the information.
Git Discussion
Cookie options tell the authentication middleware how the cookie works in the browser.In the Startup class, add this code in your ConfigureServices Method
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options => { options.LoginPath = "/Login"; });
I'm trying to seed my Identity database with a static class.
Here is my seed method:
public static async Task CreateIdentityUsers(IServiceProvider service,IConfiguration configuration)
{
var userManager = service.GetRequiredService<UserManager<AlGecUser>>();
var roleManager = service.GetRequiredService<RoleManager<AlGecRole>>();
var username = configuration["Data:AdminUser:username"];
var email = configuration["Data:AdminUser:email"];
var password = configuration["Data:AdminUser:password"];
var role = configuration["Data:AdminUser:role"];
if (await userManager.FindByNameAsync(username) == null)
{
if (await roleManager.FindByNameAsync(role) == null)
{
await roleManager.CreateAsync(new AlGecRole(role));
}
AlGecUser user = new AlGecUser()
{
UserName = username,
Email = email,
Name = "SampleName",
Surname = "SampleSurname"
};
IdentityResult result = await userManager.CreateAsync(user, password);
if (result.Succeeded)
{
await userManager.AddToRoleAsync(user, role);
}
}
}
On startup I call this method Like this :
SeedIdentity.CreateIdentityUsers(app.ApplicationServices, Configuration).Wait();
Here is my Startup method :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AlGecDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MainRepositoryCS")));
services.AddDbContext<AlGecIdentityDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("IdentityRepositoryCS")));
services.AddIdentity<AlGecUser,AlGecRole>()
.AddEntityFrameworkStores<AlGecIdentityDbContext>()
.AddDefaultTokenProviders();
services.AddTransient<IProductRepository, EfProductRepository>();
services.AddTransient<IUnitOfWork, EfUnitOfWork>();
services.AddTransient<IAdvertisementRepository, EfAdvertisementRepository>();
services.AddTransient<ICategoryRepository, EfCategoryRepository>();
services.AddTransient<ISubCategoryRepository, EfSubCategoryRepository>();
services.AddTransient<ICartService, CartService>();
services.AddTransient<ICartSessionService, CartSessionService>();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddSession();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSession();
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
SeedIdentity.CreateIdentityUsers(app.ApplicationServices, Configuration).Wait();
}
But while I'm trying to open my web site it throws an error called
AggregateException: One or more errors occurred. (Cannot resolve scoped service
'Microsoft.AspNetCore.Identity.UserManager`1[AlGec.ECommerce.WebUI.IdentityCore.AlGecUser]' from root provider.)
How can Ifix it?
Your seed data should not be added through the startup but rather the ef migrations command. Let it do the job it was intended to do. If you need to ensure data exists on start up, look into running the migrations at startup.
I am trying to setup authentication using cookies but SignInAsync seems to not be working.
My sign method is, at the moment at least, like this:
[HttpPost]
public async Task<IActionResult> Login(string username, string password)
{
if (username != "foo" || password != "bar")
{
return View();
}
//Sign in
var claims = new List<Claim>
{
new Claim("name", "admin")
};
var userIdentity = new ClaimsIdentity(claims, "login");
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
await HttpContext.SignInAsync(principal);
//Redirect
return RedirectToAction("home");
}
Home is an action with Authorize attribute.
If I provide the correct username and password, I can step over the SignInAsync call with no exception seemingly called. However, upon redirection, I end up back at the login page as I am still not logged in.
Startup.cs is listed below:
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/admin/login/";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=home}/{action=index}/{id?}");
});
app.UseAuthentication();
}
}
What am I missing or how can I debug/get further information on what is going wrong?
As per the comment by Tratcher, use app.UseAuthentication(); before app.UseMvc();.