Asp .Net Core policy handler not triggering - c#

Seriously what am i doing wrong here? How is it possible for this to not work if the other policy is the inverse of the other? only 3 letters on the name change and reverse boolean check....
the policy HasArranqueActivo is the one working. I used debugger to test and it triggers it.
Here are policies declared
services.AddAuthorization(options =>
{
options.AddPolicy("HasArranqueActivo", policy =>
policy.Requirements.Add(new HasArranqueActivoRequirement()
));
options.AddPolicy("HasArranqueInactivo", policy =>
policy.Requirements.Add(new HasArranqueInactivoRequirement()
));
});
As you can see both handlers are basically the same
public class HasArranqueActivoHandler : AuthorizationHandler<HasArranqueActivoRequirement>
{
private readonly NoPaperContext _context;
public HasArranqueActivoHandler(NoPaperContext context)
{
_context = context;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasArranqueActivoRequirement requirement)
{
// Do something with _context
// Check if the requirement is fulfilled.
var registoId = Convert.ToInt32(context.User.FindFirst(c => c.Type == ClaimTypes.PrimarySid).Value);
var registo = _context.Registos.Find(registoId);
if (registo.IsArranqueActivo)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class HasArranqueInactivoHandler : AuthorizationHandler<HasArranqueInactivoRequirement>
{
private readonly NoPaperContext _context;
public HasArranqueInactivoHandler(NoPaperContext context)
{
_context = context;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasArranqueInactivoRequirement requirement)
{
// Do something with _context
// Check if the requirement is fulfilled.
var registoId = Convert.ToInt32(context.User.FindFirst(c => c.Type == ClaimTypes.PrimarySid).Value);
var registo = _context.Registos.Find(registoId);
if (!registo.IsArranqueActivo)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
On my page i have it wrotten and it does not trigger the policy handler and i keep getting access denied. Why?
[Authorize(AuthenticationSchemes = "ProductionAuth", Policy = "HasArranqueInactivo")]
EDIT
this is the whole startup
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()
.AddCookie("ProductionAuth", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.LoginPath = new PathString("/Production/Index");
options.LogoutPath = new PathString("/Production/Logout");
options.AccessDeniedPath = new PathString("/Production/AccessDenied/");
options.SlidingExpiration = true;
})
.AddCookie("AdministrationAuth", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.LoginPath = new PathString("/Administration/Index");
options.LogoutPath = new PathString("/Administration/Logout");
options.AccessDeniedPath = new PathString("/Administration/AccessDenied/");
options.SlidingExpiration = true;
});
services.AddAuthorization(options =>
{
options.AddPolicy("HasArranqueActivo", policy =>
policy.Requirements.Add(new HasArranqueActivoRequirement()
));
options.AddPolicy("HasArranqueInactivo", policy =>
policy.Requirements.Add(new HasArranqueInactivoRequirement()
));
});
services.AddSingleton<IFileProvider>(
new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/files")));
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Administration", "/Account");
options.Conventions.AuthorizeAreaFolder("Production", "/Account");
})
.AddNToastNotifyToastr(new ToastrOptions()
{
ProgressBar = true,
TimeOut = 3000,
PositionClass = ToastPositions.TopFullWidth,
PreventDuplicates = true,
TapToDismiss = true
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddTransient<IAuthorizationHandler, HasArranqueActivoHandler>();
services.AddRouting(options =>
{
options.LowercaseUrls = true;
options.LowercaseQueryStrings = true;
});
services.AddDbContext<NoPaperContext>(options =>
{
//if(Environment.IsProduction())
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 2,
maxRetryDelay: TimeSpan.FromSeconds(1),
errorNumbersToAdd: null);
});
//else if(Environment.IsDevelopment())
//options.UseInMemoryDatabase(databaseName: "AbastecimentoDB");
});
services.AddHttpContextAccessor();
services.AddTransient<IComponenteService, ComponenteService>();
services.AddTransient<IReferenciaService, ReferenciaService>();
services.AddTransient<IRegistoService, RegistoService>();
services.AddTransient<IParagemService, ParagemService>();
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseNToastNotify();
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}

For HasArranqueActivoHandler, you've added a service registration in ConfigureServices:
services.AddTransient<IAuthorizationHandler, HasArranqueActivoHandler>();
You haven't added a service registration for HasArranqueInactivoHandler. Add the following:
services.AddTransient<IAuthorizationHandler, HasArranqueInactivoHandler>();
Implementations of IAuthorizationHandler are resolved from the DI container, so this missing registration means the authz system cannot handle the HasArranqueInactivoRequirement requirement you've set for the HasArranqueInactivo policy.

Related

Asp.net core 3.1 Authorization not working!!! Always return 401 even when token is provided

I am basically following the instruction on this youtube video https://www.youtube.com/watch?v=s2zJ_g-iQvg&ab_channel=CodAffection my problem is that I am using ASP.NET CORE while he is using ASP.NET CORE 2.2. And for some reason my api calls allways return 401 despite me having change only some line of code to make it compatible with ASP.NET CORE 2.2.
I want to make API calls when the user is login I am using ASP.NET CORE 3.1. I have made a login function which works. When I make an API call with postman the login function returns a token. However when I call another route (clients) and provided the bearer token I get a 401 error. I have searched for days but am unable to solve the problem. I have tried many different tutorials but I keep having the same problem. Thank you for your help.
[HttpPost, Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel user)
{
var userFromDb = await _userManager.FindByNameAsync(user.UserName);
if (user != null && true)//await _userManager.CheckPasswordAsync(user, user.Password))
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim("UserID", "1")
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superlongKeyWithALotOfWordsToMakeItMoreSecureWhichIsGoodThankYouForReadingMySecretKey#45")), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
{
return Unauthorized();
}
}
Here is what my startup file looks like
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<ApplicationSettings>(Configuration.GetSection("AppSettings"));
services.AddMvc();
services.AddTransient<DatabaseMigrator>();
services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
Configuration.GetConnectionString("DefaultConnection"),
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
services.TryAddScoped<UserManager<Employee>>();
services.TryAddScoped<SignInManager<Employee>>();
services.AddIdentityCore<Employee>(options =>
options.SignIn.RequireConfirmedAccount = false
).AddEntityFrameworkStores<erp_colombiaDbContext>();
//Jwt Authentication
var key = Encoding.UTF8.GetBytes("superlongKeyWithALotOfWordsToMakeItMoreSecureWhichIsGoodThankYouForReadingMySecretKey#45");//Configuration["AppSettings:Secret"].ToString());
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 4;
});
services.AddCors();
//Jwt Authentication
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// 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("/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();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
}
and here is part of my appSettings.json setings.
"AppSettings": {
"Secret": "superlongKeyWithALotOfWordsToMakeItMoreSecureWhichIsGoodThankYouForReadingMySecretKey#45",
"ClientURL": "https://localhost:44344"
},
Here is the other route (clients)
// GET api/clients
[HttpGet]
[Authorize]
public IEnumerable<ClientViewModel> Get()
{
ClientViewModel clientViewModel;
List<ClientViewModel> listClientViewModels = new List<ClientViewModel>();
var clients = Task.Run(async () => await _clientService.GetAllClients()).Result;
foreach (var client in clients)
{
clientViewModel = new ClientViewModel();
clientViewModel.ClientId = client.ClientId;
clientViewModel.Active = client.Active;
clientViewModel.Address = client.Address;
clientViewModel.City = client.City;
clientViewModel.ClienteName = client.ClienteName;
clientViewModel.ComercialEmployeeId = client.ComercialEmployeeId;
clientViewModel.Confirmed = client.Confirmed;
clientViewModel.CountryId = client.CountryId;
clientViewModel.CreationDate = client.CreationDate;
clientViewModel.DANE = client.DANE;
clientViewModel.Department = client.Department;
clientViewModel.ElectronicBillingEmail = client.ElectronicBillingEmail;
clientViewModel.Eliminated = client.Eliminated;
clientViewModel.NIT = client.NIT;
clientViewModel.PostalCode = client.PostalCode;
clientViewModel.Phone = client.Phone;
listClientViewModels.Add(clientViewModel);
}
return listClientViewModels;
}
Here is how I add the token in postman.
fix your startup, add before app.UseEndpoints
....
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
.....
Add the options.Events to your AddJwtBearer:
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = c =>
{
// break point here
return Task.CompletedTask;
},
};
});
Then you'll get some context.exception:
(right-click image, open in new window/tab)
You should never do this on asp.net
// Task.Run is a no no
Task.Run(async () => await _clientService.GetAllClients()).Result;
You're using locking up 2 threads at the same time for no benefit.
Instead:
// GET api/clients
[HttpGet]
[Authorize]
public Task<IEnumerable<ClientViewModel>> Get()
and
var clients = await _clientService.GetAllClients();
For .Net Core you need to use a authorization provider like Identity Server:
https://identityserver4.readthedocs.io/en/latest/
I figure it out I had this code that overwrite the [Authorize] attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = (User)context.HttpContext.Items["User"];
if (user == null)
{
// not logged in
context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
}
}
}
I removed it and now it work I dont understand why the code was there. Sorry, for my question.

Configuring cookies in Asp.Net Core 3.1 with Identity

I'm using Asp.Net Core 3.1 with Identity. And here is my all configuration in Startup class.
I'm trying to force the logged in user to logout if their accounts expired while using the app. I should configure the cookie correctly but i'm stuck on how to do this while having AddIdentity.
Here is my Startup
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>(options => {
options.SignIn.RequireConfirmedAccount = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityCore<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
//.AddDefaultTokenProviders()
.AddDefaultUI();
services.AddSingleton<IEmailSender, EmailSender>();
services.Configure<EmailOptions>(Configuration);
services.AddHangfire(config => config.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));
services.AddHangfireServer();
services.AddControllersWithViews(); //?
services.AddRazorPages().AddRazorRuntimeCompilation(); //?
services.AddScoped<IExpirationJob, ExpirationJob>();
services.AddScoped<IReminderJob, EmailReminder>();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
//options.Password.RequireDigit = true;
//options.Password.RequireLowercase = true;
//options.Password.RequireNonAlphanumeric = true;
//options.Password.RequireUppercase = true;
//options.Password.RequiredLength = 6;
//options.Password.RequiredUniqueChars = 1;
// Lockout settings.
//options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
//options.Lockout.MaxFailedAccessAttempts = 5;
//options.Lockout.AllowedForNewUsers = true;
// User settings.
//options.User.AllowedUserNameCharacters =
// "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+ ";
//options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "my.Cookie";
config.LoginPath = "/Home/Login";
config.AccessDeniedPath = "/Identity/Account/AccessDenied";
config.Cookie.HttpOnly = true;
config.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,
IRecurringJobManager recurringJobManager,
IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
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.UseHangfireDashboard();
//app.UseHangfireDashboard("/hangfire", new DashboardOptions()
//{
// Authorization = new[] { new CustomAuthorizeFilter() }
//});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseAuthentication();
app.UseAuthorization();
recurringJobManager.AddOrUpdate(
"End Users Subscription",
() => serviceProvider.GetService<IExpirationJob>().SetExpired(),
Cron.Minutely
);
recurringJobManager.AddOrUpdate(
"Send End of Subscription Reminder",
() => serviceProvider.GetService<IReminderJob>().SendReminder(),
Cron.Daily
);
app.Use(async (context, next) =>
{
_ = ExpirationJob.SetExpired(context);//pass the HttpContext to SetExpired
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
Here is my ValidateAsync class
public class ValidateAsync
{
public static async Task ValidatingAsync(CookieValidatePrincipalContext context)
{
context = context ?? throw new ArgumentNullException(nameof(context));
var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
{
await RejectPrincipal();
return;
}
UserManager<IdentityUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
var user = await userManager.FindByNameAsync(context.Principal.FindFirstValue(ClaimTypes.NameIdentifier));
if (user == null || user.SecurityStamp != context.Principal.FindFirst(new ClaimsIdentityOptions().SecurityStampClaimType)?.Value)
{
await RejectPrincipal();
return;
}
async Task RejectPrincipal()
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
Here is my SetExpired method
public interface IExpirationJob
{
Task SetExpired();
}
public class ExpirationJob : IExpirationJob
{
private readonly ApplicationDbContext _db;
private readonly IEmailSender _emailSender;
private readonly HttpContext _context;
public ExpirationJob(ApplicationDbContext db, IEmailSender emailSender, HttpContext context)
{
_db = db;
_emailSender = emailSender;
_context = context;
}
public async Task SetExpired()
{
foreach(var item in _db.Institution)
{
if (item.SubsEndDate != null)
{
if (item.SubsEndDate <= DateTime.Now)
{
item.Status = SD.StatusExpired;
Guid securityStamp = Guid.NewGuid();
item.Admin.SecurityStamp = securityStamp.ToString();
_context.Response.Cookies.Append("my.Cookie", "expired");
}
}
}
await _db.SaveChangesAsync();
}
}
After understanding the business logic, you can get the HttpContext in the middleware, and modify the cookie value to force logout. Here is an example.
First, configure basic cookies in ConfigureServices.
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "my.Cookie";
config.LoginPath = "/Home/Login";
config.AccessDeniedPath = "/Identity/Account/AccessDenied";
config.Cookie.HttpOnly = true;
config.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
});
Second, configure the middleware.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
IExpirationJob expirationJob)
{
//...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.Use(async(context,next)=>
{
expirationJob.SetExpired(context);//pass the HttpContext to SetExpired
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Three, determine whether to overwrite the cookie by the expiration time SubscriptionEndDate of the database.
public async Task SetExpired(HttpContext httpContext)
{
foreach (var item in _db.Institution)
{
if (item.SubscriptionEndDate != null)
{
if (item.SubscriptionEndDate == DateTime.Today)
{
item.Status = SD.StatusExpired;
Guid securityStamp = Guid.NewGuid();
item.SecurityStamp = securityStamp;
httpContext.Response.Cookies.Append("my.Cookie", "expired");
}
}
}
await _db.SaveChangesAsync();
}
Then, the cookie is invalid and this user will not access the resource.
Update:
An another method, if you want to pass the HttpContext to SetExpired as a parameter.
You can write a static class, like this:
public static class MyClass
{
public static HttpContext http { get; set; }
}
Then, you can assign values to http in Statup.cs.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
IExpirationJob expirationJob)
{
//...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.Use(async(context,next)=>
{
MyClass.http = context;
expirationJob.SetExpired();
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Get it with this.
public async Task SetExpired()
{
var path=MyClass.http.Request.Path;
//foreach (var item in _db.Institution)
//{
// if (item.SubsEndDate != null)
// {
//...
await _db.SaveChangesAsync();
}

How to work with SignalR in ASP.NET Core 3.0

We're working on an asp.net core project that depends on SignalR. Lastly we updated our project from ASP.NET Core 2.2 to 3.0 and SignalR stopped working. In the documentation we configured everything (I think correctly) but is still doesn't work. What did we miss?
ASP.NET Core 3.0 API
Startup:
public class Startup
{
private readonly string corsPolicy = "CorsPolicy";
private static string[] AllowdOrigins() => return new string[] {"localhost:4200","example.com"};
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
ConfigureAuthentication(services);
///MICROSOFT SQL DATABASE
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")
));
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("XX.XX.XX.XX"));
});
services.AddSignalR();
services.AddControllers();
//services dependencies
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(corsPolicy);
//app.UseForwardedHeaders(new ForwardedHeadersOptions
//{
// ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
//});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ChatHub>("/chat");
});
DummyData.Initialize(app);
}
private void ConfigureAuthentication(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(corsPolicy,
builder =>
{
builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin()
.AllowCredentials()
.WithOrigins(AllowdOrigins());
});
});
services.AddHttpContextAccessor();
// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
}
Chathub:
[EnableCors("CorsPolicy")]
public class ChatHub : Hub
{
private static Dictionary<string, int> onlineClientCounts = new Dictionary<string, int>();
private static readonly string FrontPrefix = "_FRONT";
public ChatHub()
{
}
[HubMethodName("ConnectFrontend")]
public async Task ConnectFrontend(int sessionId)
{
//////
}
[HubMethodName("ConnectDevice")]
public async Task ConnectDevice(int sessionId)
{
//// This method should be called but it isn't.
}
public void DisConnect(int sessionId, int userId)
{
//////////
}
[HubMethodName("SendJsonToFrontends")]
public async Task SendJsonToFrontends(int sessionId, string jsonString)
{
///
}
[HubMethodName("SendJsonToAll")]
public async Task SendJsonToAll(int sessionId, string jsonString)
{
////
}
}
Angular project
SignalRService:
export class SignalRService {
private connection: signalR.HubConnection;
public newMessage = new Subject<SocketMessage>();
constructor() {
}
public connectFront() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:2525/chat")//(environment.baseSignalR)
.configureLogging(signalR.LogLevel.Trace)
.build();
this.connection.on("ReceiveJson", data => { this.handleJsonMessage(data) });
// handles the first connection message
this.connection.start().then(() => this.sendConnectionMessage());
// handles the incoming messages
this.connection.on("ReceiveJson", data => this.handleJsonMessage(data));
this.connection.on("ReceiveJsonFrontend", data => this.handleJsonMessage(data));
}
private sendConnectionMessage() {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("ConnectFrontend", sessionId).catch((error) => { debugger; console.log(error); });
}
}
public sendWebsocketMessageToAll(msg: SocketMessage) {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("SendJsonToAll", sessionId, JSON.stringify(msg)).catch((error) => console.log(error));
}
}
public sendWebsocketMessageToFrontend(msg: SocketMessage) {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("SendJsonToFrontends", sessionId, JSON.stringify(msg)).catch((error) => console.log(error));
}
}
private handleJsonMessage(data: string) {
this.newMessage.next(this.getSocketMessage(data));
}
public getSocketMessage(data: string): SocketMessage {
var msg: SocketMessage = JSON.parse(data);
return msg;
}
public disconnect() {
this.connection.stop();
}
}
angular output:
Api output:
Just create an web app API with angular template you can view my code for your ref
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddSignalR().AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.WriteIndented = false;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapHub<ChatHub>("/chatHub");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
In FE side. Note: use new package #microsoft/signalr
import * as signalR from "#microsoft/signalr";
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
title = 'app';
ngOnInit() {
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.build();
connection.on("receiveMessage", (username: string, message: string) => {
console.log(username);
console.log(message);
});
connection.start().then(() => {
connection.send("sendMessage", "aaa", "aaaa")
.then(() => console.log("done"));
}).catch(err => document.write(err));
}
}
Well I finaly figured it out with the help of Tony,
Apperently It went wrong in the methods of the SignalR chat hub. The methods allowed only integers as parameters but it needed to be strings. I don't know it it has to do with some other settings but my guess now is that signalr cant convert the request parameters to something other then strings.
When I changed it to strings it worked.
It seems, that you have configured JWT based authentication on the server side and do not provide a token for SignalR connection. Try to provide a token using the accessTokenFactory:
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${environment.urlAddress}/chathub`, {
accessTokenFactory: () => this.token
})
.build()

Can't access client application

This application use Identity 4 with client mvc application and IDP(Identity provider) asp.net core web application.
Can't access Controller Index action view.
How to fix this Issue ????
IDP project Startup project (localhost:44393)
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.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients());
}
// 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.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
//app.Run(async (context) =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
}
}
Configure.cs file
public static class Config
{
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId ="d866oef",
Username ="Kasunjith",
Password="password",
Claims= new List<Claim>
{
new Claim("given_name","Kasunjith"),
new Claim("family_name","Underwood"),
}
}, new TestUser
{
SubjectId ="d866omf",
Username ="BimalJith",
Password="password",
Claims= new List<Claim>
{
new Claim("given_name","BimalJith"),
new Claim("family_name","ViewWord"),
}
},
};
}
// identity-related resources (Scopes)
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>()
{
new Client
{
ClientName="Image Galary",
ClientId="imagegalleryclient",
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = new List<string>()
{
"https://localhost:44335/signin-oidc"
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId
},
ClientSecrets =
{
new Secret("secret".Sha256())
}
}
};
}
}
Client application (localhost:44335)
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.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies",
(options) =>
{
}).AddOpenIdConnect("oidc", options => {
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:44393";
options.ClientId = "imagegalleryclient";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.ClientSecret = "secret";
});
}
// 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.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Gallery}/{action=Index}/{id?}");
});
}
}
My Controller class
[Authorize]
public class GalleryController : Controller
{
public async Task<IActionResult> Index()
{
await WriteOutIdentityInformation();
return View();
}
public async Task WriteOutIdentityInformation()
{
var identityToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
Debug.WriteLine($"Identity token:{identityToken}");
foreach (var claim in User.Claims)
{
Debug.WriteLine($"Claim type:{ claim.Type} -Claim value : {claim.Value}");
}
}
}
first after login using user name And Password
After go to localhost:44335/Gallary/index show this error
Not 100% sure on this, but I think by default AddOpenIdConnect will request OpenId and Profile scope, however, you've only granted your client OpenId scope, so need to add another one.
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},

.net Core Integration Test : API Controller Action Not getting called from Test. How to mock opeind connect authentication in test project?

start up cs file .net core: (This is also get called while creating test server)
public class Startup
{
private IHostingEnvironment env;
private Dictionary<string, string> secretlist = new Dictionary<string, string>();
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
this.Configuration = configuration;
this.CurrentEnvironment = env;
}
public Startup(IHostingEnvironment env)
{
this.env = env;
}
public IConfiguration Configuration { get; }
private IHostingEnvironment CurrentEnvironment { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = int.MaxValue;
x.MultipartHeadersLengthLimit = int.MaxValue;
});
services.AddApplicationInsightsTelemetry(this.Configuration);
services.AddSingleton<ITelemetryInitializer, AppInsightsInitializer>();
// Adds services required for using options.
services.AddOptions();
services.Configure<AppSettingsConfig>(this.Configuration.GetSection("AppSettings"));
if (this.CurrentEnvironment.IsDevelopment())
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Environment.ExpandEnvironmentVariables(this.Configuration.GetValue<string>("AppSettings:KeyStorage_UNCPath"))))
.ProtectKeysWithDpapiNG();
}
else
{
CloudStorageAccount storageAccount = new CloudStorageAccount(
new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(
this.Configuration.GetValue<string>("AppSettings:StorageAccountName"),
this.Configuration.GetValue<string>("AppSettings:StorageAccessValue")), true);
//Create blob client
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// Get a reference to a container named "keycontainer."
CloudBlobContainer container = blobClient.GetContainerReference("keycontainer");
services.AddDataProtection().PersistKeysToAzureBlobStorage(container, "keys.xml");
}
services.Configure<AppSettingsConfig>(options =>
{
if (!this.CurrentEnvironment.IsDevelopment())
{
}
});
var azureAdConfig = new AzureAdConfig();
this.Configuration.GetSection("Authentication:AzureAd").Bind(azureAdConfig);
services.Configure<AzureAdConfig>(this.Configuration.GetSection("Authentication:AzureAd"));
var connectionStringsConfig = new ConnectionStringsConfig();
connectionStringsConfig.oneConnection = this.secretlist["ConnectionStrings"];
//this.Configuration.GetSection("ConnectionStrings").Bind(connectionStringsConfig);
//services.Configure<ConnectionStringsConfig>(this.Configuration.GetSection("ConnectionStrings"));
if (this.RequireAAD())
{
// Add framework services.
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
}
else
{
services.Configure<MvcOptions>(options =>
{
});
}
// Add Authentication services.
if (this.RequireAAD())
{
// Configure the OWIN pipeline to use cookie auth.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
// Configure the OWIN pipeline to use OpenID Connect auth.
// https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-openid-connect-code
.AddOpenIdConnect(options =>
{
options.ClientId = azureAdConfig.ClientId;
options.ClientSecret = azureAdConfig.ClientSecret;
options.Authority = string.Format(azureAdConfig.AADInstance, azureAdConfig.Tenant);
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.Resource = azureAdConfig.ResourceURI_Graph;
// PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
options.Events = new AuthEvents(azureAdConfig, connectionStringsConfig);
});
if (this.RequireAAD())
{
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new Microsoft​.AspNetCore​.Mvc​.Authorization.AuthorizeFilter(policy));
config.Filters.Add(typeof(ExceptionFilter));
});
}
else
{
services.AddMvc();
}
if (this.Configuration.GetValue<bool>("API: SWITCH_ENABLE_API", true))
{
//services.AddScoped<IDBOperation, Operations>();
services.AddScoped<ILookupSearch, Operations>();
services.AddScoped<IFullSearch, Operations>();
}
services.AddSingleton<Common.Data.RepositoryFactories>(new Common.Data.RepositoryFactories(new Dictionary<Type, Func<DbContext, object>>
{
{ typeof(IQueryRepository), dbcontext => new QueryRepository(dbcontext) },
{ typeof(IDomainValuesRepository), dbcontext => new DomainValuesRepository(dbcontext) },
{ typeof(IRequestsRepository), dbcontext => new RequestsRepository(dbcontext) },
// { typeof(IoneDomainValuesRepository), dbcontext => new oneDomainValuesRepository(dbcontext) }
}));
services.AddTransient<Common.Contracts.IRepositoryProvider, Common.Data.RepositoryProvider>();
services.AddScoped<one.Data.Contracts.IoneUow, one.Data.oneUow>();
services.AddTransient<IUow,Uow>();
// For accessing appinsights for dependency injection?
services.AddApplicationInsightsTelemetry();
// For Storing Tokens in DB
services.AddDistributedSqlServerCache(o =>
{
o.ConnectionString = this.secretlist["ConnectionStrings"];
// o.ConnectionString = this.Configuration.GetConnectionString("oneConnection");
// o.ConnectionString = this.Configuration[this.Configuration.GetSection("KeyVaultSeetings")["oneConnectionString"]];
o.SchemaName = "dbo";
o.TableName = "CacheTable";
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IEntityExtractor, EntityExtractor>();
services.AddScoped<ITokenCacheService, DistributedTokenCacheService>();
services.AddScoped<ITokenService, TokenService>();
services.AddTransient<IAPIClient,APIClient>();
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
/// <param name="loggerFactory"></param>
/// <param name="tc"></param>
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, TelemetryClient tc)
{
var azureAdConfig = new AzureAdConfig();
this.Configuration.GetSection("Authentication:AzureAd").Bind(azureAdConfig);
loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddProvider(new MyFilteredLoggerProvider(tc));
loggerFactory.AddApplicationInsights(app.ApplicationServices, this.Configuration.GetValue<string>("Logging:LogLevel:Default") == "Information" ? Microsoft.Extensions.Logging.LogLevel.Information : Microsoft.Extensions.Logging.LogLevel.Warning);
this.SetupStore(app);
app.UseRewriter(new RewriteOptions().AddRedirectToHttps());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// TODO . Switch
app.UseStaticFiles();
if (this.RequireAAD())
{
app.UseAuthentication();
}
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
Controller is decorated as :
[Route("api/[controller]")]
public class SearchController : BaseController
Controller Action is decorated as :
[Route("TestMethod")]
[ActionName("TestMethod")]
[HttpGet]
public async Task<EmptyResult> Test()
Configuration of TestServer Test CS file :
public DemoTest()
{
// Set up server configuration
var configuration = new ConfigurationBuilder()
.AddJsonFile(#"appsettings.json")
.Build();
// Create builder
var builder = new WebHostBuilder()
.UseStartup<Startup>()
.UseConfiguration(configuration);
// Create test server
var server = new TestServer(builder);
// Create database context
this._context = server.Host.Services.GetService(typeof(DBContext)) as DBContext;
// Create client to query server endpoints
this._client = server.CreateClient();
_client.BaseAddress = new Uri("https://localhost:44316/");
}
Test as a Fact :
[Fact]
public async Task Test()
{
try
{
var response = await this._client.GetAsync("/api/Search/TestMethod");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
//Assert.False(result != null);
}
catch (Exception ex)
{
throw;
}
}
Getting Status as 302 and SearchController action is not getting
called. All the dependencies are resolved using start up configuration
file
Any idea ???
You could check the content for var responseString = await response.Content.ReadAsStringAsync(); to see what the content is.
I assume it is the login page which is due to that you required Authorize.
First, try to remove the code below for a try.
services.AddMvc(config =>
{
//var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
//config.Filters.Add(new Microsoft​.AspNetCore​.Mvc​.Authorization.AuthorizeFilter(policy));
config.Filters.Add(typeof(ExceptionFilter));
});
For Authentication, you will need to imitate the login process, here is a link for Identity, you could try implement your own login for AAD.
Razor Pages .NET Core 2.1 Integration Testing post authentication

Categories