I am building a website in dotnet core and have recently started using claims based authentication and authorization.
In a view component I am checking if the user has access to a policy.
public NavigationViewComponent(
IContextManager contextManager,
IAuthorizationService authorizationService)
{
_contextManager = contextManager;
_authorizationService = authorizationService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var showAdmin = _contextManager.Principal != null &&
(await _authorizationService.AuthorizeAsync(_contextManager.Principal, "Admin")).Succeeded;
var vm = new NavigationViewModel
{
ShowAdmin = showAdmin
};
return View(vm);
}
However, I am receiving the Exception InvalidOperationException: No policy found: Admin..
My startup.cs contains the following inside the ConfigureServices method:
services.AddAuthorization(options =>
{
options.AddPolicy("Admin",
policy => policy.Requirements.Add(new HasPermissionRequirement("ADMIN")));
});
What else do I need to configure in order to get this to work correctly?
For reference I am registering 3 additional IAuthorizationHandler implementations and 1 IAuthorizationPolicyProvider.
Edit
For reference, the whole startup.cs looks something similar to this.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
// Name and policy settings
options.Cookie.Name = "account";
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.HttpOnly = true;
// Sign the user out after 28 days of inactivity
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromDays(28);
// Challenge actions
options.LoginPath = new PathString("/account/login");
options.ReturnUrlParameter = "returnUrl";
});
services.AddAuthorization(options =>
{
options.AddPolicy("Admin",
policy => policy.Requirements.Add(new HasPermissionRequirement("ADMIN")));
});
services.AddSingleton<IAuthorizationHandler, HasPermissionHandler>();
services.AddSingleton<IAuthorizationHandler, StrategyAuthorizationCrudHandler>();
services.AddSingleton<IAuthorizationHandler, UserAuthorizationCrudHandler>();
services.AddSingleton<IAuthorizationPolicyProvider, HasPermissionPolicyProvider>();
// AddAntiforgery, AddSession,AddDistributedRedisCache and AddDataProtection omitted
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling =
ReferenceLoopHandling.Ignore;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseCookiePolicy(new CookiePolicyOptions
{
CheckConsentNeeded = httpContext => false,
MinimumSameSitePolicy = SameSiteMode.Strict,
Secure = CookieSecurePolicy.SameAsRequest
});
app.UseAuthentication();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseStaticFiles();
var supportedCultures = new[]
{
new CultureInfo("en-GB")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(supportedCultures[0]),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
app.UseSession();
app.UseMiddleware<UserMiddleware>();
app.UseMiddleware<LoggingMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
HasPermissionRequirement.cs
public class HasPermissionRequirement : IAuthorizationRequirement
{
public string Permission { get; private set; }
public HasPermissionRequirement(string permission)
{
Permission = permission;
}
}
HasPermissionHandler.cs
public class HasPermissionHandler : AuthorizationHandler<HasPermissionRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
HasPermissionRequirement requirement)
{
var hasPermission = context.User.HasClaim("Permission", requirement.Permission);
if (hasPermission)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
HasPermissionPolicyProvider.cs
public class HasPermissionPolicyProvider : IAuthorizationPolicyProvider
{
private const string PolicyPrefix = "HasPermission";
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (!policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
return Task.FromResult<AuthorizationPolicy>(null);
var permission = policyName.Substring(PolicyPrefix.Length);
var policy = new AuthorizationPolicyBuilder();
policy.AddRequirements(new HasPermissionRequirement(permission));
return Task.FromResult(policy.Build());
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
}
I've managed to solve this by taking a closer look at the Microsoft documentation after being pointed in the right direction.
https://github.com/aspnet/Docs/blob/master/aspnetcore/security/authorization/iauthorizationpolicyprovider.md#multiple-authorization-policy-providers
When using custom IAuthorizationPolicyProvider implementations, keep in mind that ASP.NET Core only uses one instance of IAuthorizationPolicyProvider. If a custom provider isn't able to provide authorization policies for all policy names, it should fall back to a backup provider.
As a result I changed the implementation of my HasPermissionPolicyProvider to CustomPolicyProvider and the content is below:
public class CustomPolicyProvider : DefaultAuthorizationPolicyProvider
{
private const string PermissionPolicyPrefix = "HasPermission";
public CustomPolicyProvider(IOptions<AuthorizationOptions> options) : base(options)
{
}
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
var policy = await base.GetPolicyAsync(policyName);
if (policy != null) return policy;
if (policyName.StartsWith(PermissionPolicyPrefix, StringComparison.OrdinalIgnoreCase))
{
var permission = policyName.Substring(PermissionPolicyPrefix.Length);
return new AuthorizationPolicyBuilder()
.AddRequirements(new HasPermissionRequirement(permission))
.Build();
}
return null;
}
}
This means that you can only have one PolicyProvider which must handle all your logic. The change is to ensure that it calls the default implementation if you require multiple handler logic.
Related
I have a api endpoints that allows me to login. Then I have a other api endpoint where I want to get data from.
Here is a exemple of data that I want to get from my postman As you can see when I use the login endpoint I get a token
Then I want to use the token to get the clients but it does not work I get A 401 here is a image of the problem. As you can see I have used the token but I am still not authorized why is that? Thank you for your help.
I have the following Controller to authenticate.
[Route("api/auth")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;
private readonly IEmployeeService _employeeService;
public AuthController(ILogger<AuthController> logger, IEmployeeService employeeService)
{
_logger = logger;
_employeeService = employeeService;
}
[HttpPost, Route("login")]
public IActionResult Login([FromBody] LoginModel user)
{
Employee employeeFromDB = Task.Run(async () => await _employeeService.GetByUserName(user.UserName)).Result;
if (user == null)
{
return BadRequest("Invalid client request");
}
if(employeeFromDB == null)
{
return Unauthorized();
}
else if (user.UserName == employeeFromDB.UserName && user.Password == "123qwe")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#45"));
var signingCreditials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokenOptions = new JwtSecurityToken(
issuer: "https://localhost:44363/",
audience: "https://localhost:44363/",
claims: new List<Claim>(),
expires: DateTime.Now.AddMinutes(5),
signingCredentials: signingCreditials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
return Ok(new { Token = tokenString });
}
return Unauthorized();
}
I have the following controller that is secured
[Route("api/[controller]")]
[ApiController]
public class ClientsController : ControllerBase
{
private readonly ILogger<ClientsController> _logger;
private readonly IClientService _clientService;
public ClientsController(ILogger<ClientsController> logger, IClientService clientService)
{
_logger = logger;
_clientService = clientService;
}
// 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 my startup code
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.AddCors(options =>
{
options.AddPolicy("EnableCORS", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(opttions =>
{
opttions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://localhost:44363",
ValidAudience = "https://localhost:44363",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#45"))
};
});
services.AddControllersWithViews();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<DatabaseMigrator>();
services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
Configuration.GetConnectionString("DefaultConnection"),
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
Configuration.GetConnectionString("DefaultConnection"),
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCors("EnableCORS");
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
following problem I can create a token but when I use it the authentication failed, I try to copy from an example but I had problems because
some methods are not "there". (like principal.SetScopes, but it seems to be exist in the Github repository and in other examples)
The only error I get is the failure of the AuthorizationFilter.
Here the method for creating the token
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest connectRequest)
{
if (connectRequest.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(connectRequest.Username);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIdConnectConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIdConnectConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
}));
}
var result = await _signInManager.CheckPasswordSignInAsync(user, connectRequest.Password, lockoutOnFailure: true);
if (!result.Succeeded)
{
return Forbid(
authenticationSchemes: OpenIddictValidationDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIdConnectConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIdConnectConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
}));
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
//principal.SetScopes(new[]
//{
// Scopes.OpenId,
// Scopes.Email,
// Scopes.Profile,
// Scopes.Roles
//}.Intersect(connectRequest.GetScopes()));
//foreach (var claim in principal.Claims)
//{
// claim.SetDestinations(GetDestinations(claim, principal));
//}
var sign = SignIn(principal, OpenIddictServerDefaults.AuthenticationScheme);
return sign;
}
throw new Exception("Not supported");
}
Here is my startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DocumentiveContext>((provider, builder) =>
{
var configuration = provider.GetService<IConfiguration>();
builder.UseSqlServer(configuration.GetConnectionString("connectionString"));
builder.UseOpenIddict();
});
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
})
.AddEntityFrameworkStores<DocumentiveContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
});
services.AddAuthentication(options =>
options.DefaultScheme = OpenIddictValidationDefaults.AuthenticationScheme);
services.AddOpenIddict(builder =>
{
builder.AddCore(coreBuilder => coreBuilder.UseEntityFrameworkCore().UseDbContext<DocumentiveContext>());
builder.AddServer(serverBuilder =>
{
serverBuilder.UseMvc();
//serverBuilder.EnableTokenEndpoint("/connect/token");
serverBuilder.EnableAuthorizationEndpoint("/connect/authorize")
.EnableLogoutEndpoint("/connect/logout")
.EnableTokenEndpoint("/connect/token")
.EnableUserinfoEndpoint("/connect/userinfo")
.EnableUserinfoEndpoint("/connect/verify");
serverBuilder.AllowPasswordFlow();
serverBuilder.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api");
serverBuilder.AddDevelopmentSigningCertificate();
serverBuilder.AcceptAnonymousClients();
serverBuilder.DisableHttpsRequirement();
});
builder.AddValidation(validationBuilder =>
{
});
});
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddGrpc();
services.AddTransient<IUserInformationService, UserInformationService>();
}
// 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.UseRouting();
app.UseAuthentication();
app.UseStaticFiles();
app.UseSerilogRequestLogging();
app.UseAuthorization();
app.UseMvcWithDefaultRoute();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}
}
Any idea?
After updating to the newest Version 3 of Openiddict, it works.
Also I needed to set the Authentication Scheme in the attribute.
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
I need to add a Basic Authentication to a set of API.
DISCLAIMER: The APIs are inside an intranet and contains public data. They can be consumed from external users only through an API gateway where strong authentication and authorization are implemented. This Basic Authentication is needed only to avoid that internal development team calls directly this service. User and password are not the real one.
I've used the package ZNetCS.AspNetCore.Authentication.Basic, version 4.0.0
This is my startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services
.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme)
.AddBasicAuthentication(
options =>
{
options.Realm = "My Application";
options.Events = new BasicAuthenticationEvents
{
OnValidatePrincipal = context =>
{
if ((context.UserName.ToLower() == "gateway0107")
&& (context.Password == "jir6STt6437yMAQpl"))
{
var claims = new List<Claim>{
new Claim(ClaimTypes.Name,
context.UserName,
context.Options.ClaimsIssuer)
};
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(new ClaimsIdentity(
claims,
BasicAuthenticationDefaults.AuthenticationScheme)),
new Microsoft.AspNetCore.Authentication.AuthenticationProperties(),
BasicAuthenticationDefaults.AuthenticationScheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Authentication failed."));
}
};
});
services.AddMvcCore();
services.AddApiVersioning(
options =>
{
options.ReportApiVersions = true;
});
services.AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen(
options =>
{
options.OperationFilter<SwaggerDefaultValues>();
options.IncludeXmlComments(XmlCommentsFilePath);
});
services.AddCors(options =>
{
options.AddPolicy(name: "AllowAllOrigins",
builder =>
{
builder.AllowAnyOrigin();
});
});
services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Point)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Coordinate)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(LineString)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(MultiLineString)));
}).AddNewtonsoftJson(options =>
{
foreach (var converter in NetTopologySuite.IO.GeoJsonSerializer.Create(new GeometryFactory(new PrecisionModel(), 4326)).Converters)
{
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
options.SerializerSettings.Converters.Add(converter);
}
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseForwardedHeaders();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "swagger-ui")),
RequestPath = "/swagger-ui"
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("AllowAllOrigins");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
}
static string XmlCommentsFilePath
{
get
{
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml";
return Path.Combine(basePath, fileName);
}
}
}
But testing in Postman and from SwaggerUI I get always 410 Unauthorized.
Setting a breakpoint inside the OnValidatePrincipal, it never gets hit.
Should I have to add something else?
Unable to Identify the error after publishing the file
at System.Text.Encoding.GetBytes(String s)
at Inspection.Apis.Startup.ConfigureServices(IServiceCollection services) in C:\Users\me_mo\source\InspectionBackend\Inspection.Apis\Startup.cs:line 66
Line 66 is Swagger configuration
I tried to run also on release mode and its running fine.
Finding out this error just on deployment
namespace Inspection.Apis
{
public class Startup
{
public Startup(IHostingEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public IHostingEnvironment Environment { get; }
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.AddAutoMapper(typeof(AutoMapperMappings).Assembly);
services.AddDbContext<InspectionEfContext, InspectionContext>();
services.AddDbContext<InspectionContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddHttpContextAccessor();
services.AddCors(options =>
{
options.AddPolicy("AllowAll",
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
EntityFrameworkManager.ContextFactory = context =>
{
return new InspectionContext((context as InspectionContext).RequestInfo);
};
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Inspection Suite API", Version = "v1" });
options.OperationFilter<TenantParameterOperationFilter>();
options.AddSecurityDefinition("Bearer",
new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>() }
});
});
#region JWT
var key = Encoding.ASCII.GetBytes(Configuration["JwtKey"]);
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
};
});
#endregion
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
if (Environment.IsProduction())
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore;
}
});
services.AddScoped<IRequestInfo>(provider =>
{
var context = provider.GetRequiredService<IHttpContextAccessor>();
var claims = context.HttpContext.User.Claims;
return new RequestInfo(this.Configuration, int.TryParse(claims.FirstOrDefault(o => o.Type == "tenantId")?.Value, out int tenantId) ? (int?)tenantId : null);
});
services.AddScoped<RequestScope<InspectionContext>>(provider =>
{
var dbContext = provider.GetRequiredService<InspectionContext>();
var scope = provider.GetRequiredService<RequestScope>();
var userId = provider.GetRequiredService<IHttpContextAccessor>().HttpContext.User.FindFirst(x => x.Type == "UserId")?.Value;
return new RequestScope<InspectionContext>(scope.ServiceProvider, dbContext, scope.Logger, scope.Mapper, userId, scope.TenantId);
});
services.AddScoped<RequestScope<InspectionEfContext>>(provider =>
{
var dbContext = provider.GetRequiredService<InspectionEfContext>();
var scope = provider.GetRequiredService<RequestScope>();
var userId = provider.GetRequiredService<IHttpContextAccessor>().HttpContext.User.FindFirst(x => x.Type == "UserId")?. Value;
return new RequestScope<InspectionEfContext>(scope.ServiceProvider, dbContext, scope.Logger, scope.Mapper, userId, scope.TenantId);
});
services.AddScoped<RequestScope>(provider =>
{
var logger = provider.GetRequiredService<ILogger<Program>>();
var context = provider.GetRequiredService<IHttpContextAccessor>();
var claims = context.HttpContext.User.Claims;
var userId = claims.FirstOrDefault(o => o.Type == "UserId")?.Value;
var tenantId = int.TryParse(claims.FirstOrDefault(o => o.Type == "tenantId")?.Value, out int t) ? t : default(int?);
if (!tenantId.HasValue)
{
tenantId = int.TryParse(context.HttpContext.Request.Headers["tenantId"].SingleOrDefault(), out t) ? t : default(int?);
}
var mapper = provider.GetRequiredService<IMapper>();
return new RequestScope(provider, logger, mapper, userId, tenantId);
});
ConfigureRepositories(services);
ConfigureAppServices(services);
}
private void ConfigureRepositories(IServiceCollection services)
{
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IFormBuilderTypeRepository, FormBuilderTypeRepository>();
services.AddScoped<IFormBuilderRepository, FormBuilderRepository>();
services.AddScoped<IFormBuilderQuestionsRepository, FormBuilderQuestionsRepository>();
services.AddScoped<IFormBuilderQuestionsResponseRepository, FormBuilderQuestionsResponseRepository>();
}
private void ConfigureAppServices(IServiceCollection services)
{
services.AddScoped<IUserService, UserServices>();
services.AddScoped<IAuthenticationService, AuthenticationService>();
services.AddScoped<UserIdGenerator>();
services.AddScoped<IFormBuilderTypeServices, FormBuilderTypeServices>();
services.AddScoped<IFormBuilderServices, FormBuilderServices>();
services.AddScoped<IFormBuilderQuestionsServices, FormBuilderQuestionsServices>();
services.AddScoped<IFormBuilderQuestionsResponseServices, FormBuilderQuestionsResponseServices>();
}
// 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
{
// 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.UseMiddleware<InspectionExceptionMiddleware>();
app.UseHttpsRedirection();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Inspection API V1");
c.RoutePrefix = string.Empty;
});
app.UseMvc();
}
}
public class TenantParameterOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "tenantId",
In = ParameterLocation.Header,
Description = "Tenant Id"
});
}
}
}
//need to do initialization value in ---> appsettings.json <---- folder
// i did this
{
"JWT": {
"ServerSecret": "qwertyuiopasdfghjklzxcvbnm123456",
"Issuer": "https://awesome.io",
"Audience": "https://app.awesome.io"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
After Yegor Androsov views i reviewed and find out he was right
Configuration[JwtKey] was null due to it was not placed in appsetting.json
it was placed in appsetting.development.json
Also make sure when referencing the key and value in appsetting.json,
use Configuration["JwtKey:Secret"]
Working on an Angular 2/ASP.NET Core 2.1 app. Upon logging in, I can console.log the JWT token from the front-end, but when I try to access a controller (AccountsController.cs) with an [Authorize] attribute, I get a 401 error. If I remove the attribute, and I try to get the current user using var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value); I get sent immediately back to the front-end and receive an error message via the resolver.
It seems that the current user data is not being sent to the back-end with the request. Or it's not being stored? Or maybe I'm not accessing it correctly?
account-list.resolver.ts
import { Resolve, Router, ActivatedRouteSnapshot } from '#angular/router';
import { Account } from '../../_models/account';
import { Injectable } from '#angular/core';
import { AccountService } from '../../_services/account.service';
import { AlertifyService } from '../../_services/alertify.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';
#Injectable()
export class AccountListResolver implements Resolve<Account[]> {
pageSize = 5;
pageNumber = 1;
constructor(private accountService: AccountService,
private router: Router,
private alertify: AlertifyService) {
}
resolve(route: ActivatedRouteSnapshot): Observable<Account[]> {
return this.accountService.getAccounts(this.pageNumber, this.pageSize).catch(error => {
this.alertify.error('Problem retrieving data');
this.router.navigate(['/dashboard']);
return Observable.of(null);
});
}
}
account.service.ts
import { Injectable } from '#angular/core';
import { environment } from '../../environments/environment';
import { Account } from '../_models/account';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { PaginatedResult } from '../_models/pagination';
import { HttpClient, HttpParams } from '#angular/common/http';
#Injectable()
export class AccountService {
baseUrl = environment.apiUrl;
constructor(private authHttp: HttpClient) { }
getAccounts(page?, itemsPerPage?, accountParams?: any) {
const paginatedResult: PaginatedResult<Account[]> = new PaginatedResult<Account[]>();
let params = new HttpParams();
if (page != null && itemsPerPage != null) {
params = params.append('pageNumber', page);
params = params.append('pageSize', itemsPerPage);
}
// if (accountParams != null) {
// params = params.append('paramName', accountParams.paramName);
// }
return this.authHttp
.get<Account[]>(this.baseUrl + 'accounts', { observe: 'response', params })
.map(response => {
paginatedResult.result = response.body;
if (response.headers.get('Pagination') != null) {
paginatedResult.pagination = JSON.parse(response.headers.get('Pagination'));
}
return paginatedResult;
});
}
}
AccountsController
[Authorize]
[Route("api/[controller]")]
public class AccountsController : Controller
{
private readonly IBaseRepository _repo;
private readonly IMapper _mapper;
public AccountsController(IBaseRepository repo, IMapper mapper)
{
_mapper = mapper;
_repo = repo;
}
[HttpGet]
public async Task<IActionResult> GetAccounts([FromQuery] AccountParams accountParams)
{
var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
//^^^WHERE IT BREAKS WHEN AUTHORIZATION ATTRIBUTE IS REMOVED
//code to generate list of accounts to return
accountParams.UserId = currentUserId;
var accounts = await _repo.GetAccounts(accountParams);
var accountsToReturn = _mapper.Map<IEnumerable<AccountForListDto>>(accounts);
Response.AddPagination(accounts.CurrentPage, accounts.PageSize, accounts.TotalCount, accounts.TotalPages);
return Ok(accountsToReturn);
}
}
**EDIT**
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value);
services.AddDbContext<DataContext>(x => x
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b =>
b.MigrationsAssembly(("MyApp.App")))
.ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)));
services.AddMvc()
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; });
services.AddTransient<Seed>();
services.AddCors();
services.AddAutoMapper();
services.AddScoped<IAuthRepository, AuthRepository>();
services.AddScoped<IBaseRepository, BaseRepository>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddScoped<LogUserActivity>();
}
public void ConfigureDevelopmentServices(IServiceCollection services)
{
var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value);
services.AddDbContext<DataContext>(x => x
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b =>
b.MigrationsAssembly(("MyApp.App")))
.ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)));
services.AddMvc()
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; });
services.AddTransient<Seed>();
services.AddCors();
services.AddAutoMapper();
services.AddScoped<IAuthRepository, AuthRepository>();
services.AddScoped<IBaseRepository, BaseRepository>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddScoped<LogUserActivity>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
context.Response.AddApplicationError(error.Error.Message);
await context.Response.WriteAsync(error.Error.Message);
}
});
});
}
app.ConfigureSwagger(Assembly.GetExecutingAssembly());
app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials());
app.UseDefaultFiles();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{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.UseProxyToSpaDevelopmentServer("http://localhost:4200");
//spa.UseAngularCliServer(npmScript: "start");
}
});
}
Did you add the JWT authorization handler?
In your startup.cs
Is there app.UseAuthentication() in the Configure method?
Is it before app.UseMvc()?
Is there app.AddAuthentication() in your ConfigureServices method?
Is it before app.AddMvc()?
Is there a call to AddJwtBearer() in your ConfigureServices method hanging
off the call to AddAuthentication()?
Do you have the right keys in the options for the JwtBearer service?
If JwtBearer isn't your only authentication mechanism (for example you also added Identity) are you specifying the scheme name for bearer in your Authorize attribute?
From your config it looks like you're missing app.UseAuthentication() in the configure method.
So you need to put it before app.UseMvc() like so;
app.UseSpaStaticFiles();
app.UseAuthentication()
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});