I added the [Authorization] attribute to the below action to protect it from unauthorized access. I'm wondering if it's possible to return a custom error/exception for this?
In postman, it only shows the 401 status but the response of the body is empty.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[Authorize]
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
If you are using.NET 5 and above you can implement the IAuthorizationMiddlewareResultHandler and return whatever http response code and data you want on the failed authorization. You can even return different response codes and data based on different authorization policy failures that you can define yourself
public class CustomAuthorizationMiddlewareResultHandler :
IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();
public async Task HandleAsync(
RequestDelegate next,
HttpContext context,
AuthorizationPolicy policy,
PolicyAuthorizationResult authorizeResult)
{
if (policyAuthorizationResult.Forbidden &&
policyAuthorizationResult.AuthorizationFailure.FailedRequirements.OfType<Show404Requirement>().Any();)
{
var bytes = Encoding.UTF8.GetBytes("Not found bro");
await HttpContext.Response.Body.WriteAsync(bytes, 0, bytes.Length);
context.Response.StatusCode = StatusCodes.Status404NotFound;
return;
}
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy,
policyAuthorizationResult);
}
}
More details on the topic here https://learn.microsoft.com/en-us/aspnet/core/security/authorization/customizingauthorizationmiddlewareresponse?view=aspnetcore-5.0
Related
I am new to IdentityServer4. I have created a IdentityServer4 client, a scope at the IdentityServer4 running at https://localhost:44311/. I secured a sample Weather API using IdentityServer4. When I run Program.cs, I receive an Authorization token. I set this token using client.SetBearerToken(tokenResponse.AccessToken); but when I send GET request to API using await client.GetAsync($"https://localhost:44315/weatherforecast");, I receive 401 Unauthorized or 403 Forbidden. What am I missing? Here is code:
Startup.cs
namespace weatherapi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication("Bearer", options =>
{
options.ApiName = "weatherapi";
options.Authority = "https://localhost:44311/";
});
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
WeatherForecastController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace weatherapi.Controllers
{
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
Program.cs
using IdentityModel.Client;
using System.Text;
await SampleWeather();
//await SampleAdminApi();
async Task SampleWeather()
{
using var client = new HttpClient();
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = "https://localhost:44311/connect/token",
ClientId = "weatherapi",
ClientSecret = "weatherapi",
Scope = "weatherapi_scope",
GrantType = "client_credentials"
});
if (tokenResponse.IsError)
{
throw new Exception("Unable to get token", tokenResponse.Exception);
}
client.SetBearerToken(tokenResponse.AccessToken);
var response = await client.GetAsync($"https://localhost:44315/weatherforecast");
var content = await response.Content.ReadAsStringAsync();
Console.ReadLine();
}
As it seems to be a plain API, I would replace AddIdentityServerAuthentication with AddJwtBearer (see this page)
I would also set this flag to true inside AddJWtBearer
opt.IncludeErrorDetails = true;
Because then you would get some more details in the WWW-Authenticate header that you get back when you get a 401 error, like this:
HTTP/1.1 401 Unauthorized
Date: Sun, 02 Aug 2020 11:19:06 GMT
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid"
I want to try to filter data based on API key authentication using NET CORE where the key is stored in the header. each key has its own data. is there a reference that can help me with that?
for an example like this
sorry if my question is difficult to understand. thank you very much good luck always
You can use Request.Headers["ApiKey"] to get value of "ApiKey" header,
using that value do your filter logic
Below is work demo, you can refer to it.
Using the Custom Attributes, name the Attribute as ApiKeyAttribute.We will be using this attribute to decorate the controller so that any request that is routed to the attributed controller will be redirected to ApiKeyAttribute.
1.ApiKeyAttribute.cs :
public class ApiKeyAttribute : Attribute, IAsyncActionFilter
{
private const string APIKEYNAME = "ApiKey";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.HttpContext.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key was not provided"
};
return;
}
var appSettings = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var apiKey = appSettings.GetValue<string>(APIKEYNAME);
if (!apiKey.Equals(extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key is not valid"
};
return;
}
await next();
}
}
2.Add the API Key inside the appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ApiKey": "aaaaaaaaaaaaa"
}
Add [ApiKey] to WeatherForecastController
[ApiKey]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
Result:
I created a new project from the ASP.NET Core Web API template in Visual Studio, and attempted to add a new controller, but I get a 404 response with this message when trying to GET from it:
Cannot GET /navigation
My program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
The working controller that came with the template:
using Microsoft.AspNetCore.Mvc;
namespace Test.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
My new controller:
using Microsoft.AspNetCore.Mvc;
using Test.API.Navigation;
namespace Test.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class NavigationController : ControllerBase
{
public NavigationController()
{
}
[HttpGet(Name = "GetNavigation")]
public IEnumerable<NavigationItem> Get()
{
return new List<NavigationItem>
{
new NavigationItem()
{
Title = "Home",
Url = "test",
AltText = "Home page"
}
};
}
}
}
How can I get this controller to work? The project seems to have no extra config files that are relevant, just launchsettings.json and applicationsettings.json.
I tried changing the name of the WeatherForecastController, which led to it returning the same response, so it feels like it is hardcoded somehow.
Did you update your proxy.conf.js file? Change the context to /navigation. The target is your target you have now.
const PROXY_CONFIG = [
{
context: [
"/navigation",
],
target: "https://localhost:7105",
secure: false
}
]
module.exports = PROXY_CONFIG;
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Use Autofac
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Replace the default controller activator
//builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, CustomControllerActivator>());
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
CustomControllerActivator.cs
public class CustomControllerActivator : IControllerActivator
{
public object Create(ControllerContext context)
{
// `serviceProvider` the object is `Autofac.Extensions.DependencyInjection.AutofacServiceProvider` type
var serviceProvider = context.HttpContext.RequestServices;
...
}
public void Release(ControllerContext context, object controller)
{
...
}
}
WeatherForecastController.cs
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
private readonly IServiceScope serviceProvider;
public WeatherForecastController(ILogger<WeatherForecastController> logger,IServiceProvider serviceProvider)
{
_logger = logger;
//`serviceProvider` the object is `Autofac.Extensions.DependencyInjection.AutofacServiceProvider` type
serviceProvider = serviceProvider;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
I checked the source code and didn't see you replace 'IControllerActivator' or 'IControllerActivatorProvider', so I don't quite understand how you operate the activation of the controller
I don't understand how you replace the object of 'HttpContext.RequestServices' with the type of 'Autofac.Extensions.DependencyInjection.AutofacServiceProvider'
Thank you very much for asking for relief
I would like to have second controller in my asp.net WebApi, but when i add it it not works... First Controller works OK
i have 404 not found in my browser
not any errors while run
whats wrong?
namespace testing.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering",
"Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
and the second is below
{
[Route("[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public int Get()
{
return 100050;
}
}
}
Can some one tell me whats wrong?
You're using the attribute [Route("[controller]")] on your controller class. The string [controller] means "the name of the class, without the actual WORD "Controller".
This means, the name of the controller is "Values" (or "WeatherForecast" for the previous controller).
So, the final url route you want is /Values, not /ValuesController.
You can read more about how this works on the MS Docs page (the whole page has a lot of good information, not just that section).