Cannot get current user or authorize - c#

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

Related

IdentityServer4 throws 500 internal server error when using Authorize attribute on API

I have been trying to figure this out all weekend, but can't seem to get it to work. Please help.
I have setup IdentityServer 4, an MVC application and an API.
I get an access token after login, but when I try to access the API it throws 500 internal server error (if I have an Authorize attribute)
this is my identityserver config file:
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("api1", "my API")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://localhost:5002/signin-oidc" },
//FrontChannelLogoutUri = "https://localhost:5002/signout-oidc",
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
This is the identityServer startup class:
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
if (System.Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production")
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDbConnection")));
else
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>();
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
services.AddAuthentication()
.AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "xxx.apps.googleusercontent.com";
options.ClientSecret = "xxx";
options.ReturnUrlParameter = "https://xxx.azurewebsites.net/signin-google";
}).AddFacebook(facebookOptions =>
{
facebookOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
facebookOptions.ClientId = "xxx";
facebookOptions.ClientSecret = "xxx";
facebookOptions.ReturnUrlParameter = "https://xxx.azurewebsites.net/signin-facebook";
});
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
This is my API startup class:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Configuration;
namespace SF.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration
{
get;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// accepts any access token issued by identity server
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = ConfigurationManager.AppSettings["IdentityServerAddress"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
// adds an authorization policy to make sure the token is for scope 'api1'
//services.AddAuthorization(options =>
//{
// options.AddPolicy("ApiScope", policy =>
// {
// policy.RequireAuthenticatedUser();
// policy.RequireClaim("scope", "api1");
// });
//});
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.UseStaticFiles();
}
}
}
this is the Controller class:
[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
[Route("/Test")]
public IActionResult Test()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
This is the MVC application startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://xxx.azurewebsites.net";
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute()
.RequireAuthorization();
});
}
This is how I call the API:
public async Task<IActionResult> CallApi()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = await client.GetStringAsync("https://localhost:6001/Test");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
My access token looks like this:
"eyJhbGciOiJSUzI1NiIsImtpZCI6IkExNDYxOUUzOTAwNjM5ODA2NUU4RkUwQjJFMkU1RThFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NDg5ODEyMjMsImV4cCI6MTY0ODk4NDgyMywiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eXNlcnZlcmlkZW50aXR5cHJvdmlkZXJzZi5henVyZXdlYnNpdGVzLm5ldCIsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHlzZXJ2ZXJpZGVudGl0eXByb3ZpZGVyc2YuYXp1cmV3ZWJzaXRlcy5uZXQvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoibXZjIiwic3ViIjoiZDc5NGQxOGUtNGYyOC00MmE3LWFkMTQtZDdiMWMxMDcwOTE5IiwiYXV0aF90aW1lIjoxNjQ4OTgxMjIzLCJpZHAiOiJsb2NhbCIsImp0aSI6IjZBRTE3Q0RBMjBGQkNGNDExQzc3QUIyQkNBNTE3M0YzIiwic2lkIjoiRkMxREY5QUZCODQyRkZEN0JGRjk5MTY0RTYyN0M2ODYiLCJpYXQiOjE2NDg5ODEyMjMsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJhcGkxIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbInB3ZCJdfQ.Grcu-dbrLy6LHHxsW2FJsSIhmwQBEl1jQ2LRvJhBFzZ8j0HAqk129Q8JncJFSFBjQkEls8xBFN-OxyvhJ5o7dmgpkgYENbfjl7jC04yhvSh_MzLqG2h_mme1mwsC3xzuKbQR1yczei-j92WUMeP-CvzUtr2vbJd2lJv0YvpJvykGF4BbKrQMPLPZnlLFRkPm5LcdFfUsrHrCz3R0JZ7tpVSwGMjGMlDHlAMAR04Fzf6YQhbKUEydNdTIWFP2akyBoWuRwAvTXbOA8vm9GZpeTbo8S4At5X7RhOR_J-zIjk1QWKhqN9kVMnMLXpO_NmZ6iQ66pcnT0G75rtFEfFtISQ"
Is there anything wrong with how I access the token? I can't see the scopes for example?
What else could be wrong?
**Update: I added app.UseAuthorization() in the API.Startup class. Then I got 401 Unauthroized instead
One problem with the API is that it lacks
app.UseAuthentication();
app.UseAuthorization();
In the API startup class.
To debug the API in ASP.NET Core, you can try to set the log level top debug or trace, like this in appsetttings:
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.Authentication": "Trace",
"Microsoft.AspNetCore.Authorization": "Trace"
}
}
}
You should check inside the access token header:
{
"alg": "RS256",
"kid": "A14619E39006398065E8FE0B2E2E5E8E",
"typ": "at+jwt"
}
The kid key identifier must be present in the JWKS endpoint.

C# API Gives error 401 in postaman, even when using the generated token

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

WebAPI .NET Core 3 basic authentication

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 invalidate the Asp.Net cookies instantly after logout

User has logged out from the Site but APIs are accessible from POSTMAN with cookies in the header?
Logout
public async Task OnPost(string returnUrl = null)
{
await _httpContextAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new AuthenticationProperties
{
RedirectUri = returnUrl
});
await _signInManager.SignOutAsync();
HttpContext.Response.Cookies.Delete(".AspNetCore.Cookies");
}
Startup
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
HostingEnvironment = env;
}
public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Https
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(180);
options.ExcludedHosts.Add("admission.just.edu.bd");
options.ExcludedHosts.Add("www.admission.just.edu.bd");
});
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
options.HttpsPort = HostingEnvironment.IsDevelopment() ? 5001 : 443;
});
services.AddMemoryCache();
services.AddDbContext<AdmissionDbContext>(options =>
{
if (HostingEnvironment.IsDevelopment())
{
options.UseSqlServer(Configuration["DbConnection:Sql:Local"], x => x.MigrationsHistoryTable("__EFMigrationsHistory", Configuration["DbConnection:Sql:Schema"]));
}
else
{
options.UseSqlServer(Configuration["DbConnection:Sql:Cloud"], x => x.MigrationsHistoryTable("__EFMigrationsHistory", Configuration["DbConnection:Sql:Schema"]));
}
});
services.AddIdentity<ApplicationUser, IdentityRole>(
options =>
{
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(2);
})
.AddEntityFrameworkStores<AdmissionDbContext>()
.AddDefaultTokenProviders();
services.AddAuthorization(opts =>
{
opts.AddPolicy("AuthenticatedUser", policy => policy.RequireAuthenticatedUser());
opts.AddPolicy("SystemAdminOnly", policy => policy.RequireRole(SystemRole.Administrator));
opts.AddPolicy("SupportOnly", policy => policy.RequireRole(SystemRole.Support));
opts.AddPolicy("ApplicantOnly", policy => policy.RequireRole(SystemRole.Applicant));
});
services.AddScoped<IClaimsTransformation, ClaimsTransformation>();
services.AddSession();
services.AddMvc(
options =>
{
options.Filters.Add<ErrorExceptionFilter>();
}
).SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
})
.AddJsonOptions(opts =>
{
opts.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
opts.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
opts.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
if (HostingEnvironment.IsDevelopment())
{
opts.SerializerSettings.Formatting = Formatting.Indented;
}
});
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, IHostingEnvironment env)
{
//app.UseDeveloperExceptionPage();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRequestLocalization();
// app.UsePendingMigrations();
app.UseDefaultRoles(SystemRole.All);
app.UseDefaultUsers();
//app.UseCookiePolicy();
app.UseAuthentication();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute("areaRoute", "{area:exists}/{controller}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
I don't know why, but for some reason Response.Cookies.Delete(cookieKey) was not working for me. What I did was create another cookie with the same cookie name with expiry set to a time in past. Eg:
var c = new HttpCookie("cookieKey");
c.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(c);

InvalidOperationException: No policy found

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.

Categories

Resources