I have a problem with middleware hierarchy. I created very simple web api with one controller/action
[Route("api/v1/values")]
public class ValuesController : Controller
{
// GET api/v1/values/5
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
return await Task.Run<IActionResult>(() =>
{
if (id == "nf")
{
return NotFound();
}
return Ok($"value: {id}");
});
}
}
I configured routing to use Mvc if match or write Default to response if not.
app.UseMvc();
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Default");
});
And this works great, below you can see sample requests/responses:
/api/v1/values/1 -> 200, Body: "value: 1"
/api/v1/values/nf -> 404
/api/v1/values -> "Default"
Great, but then I added ApiVersioning, so controller looks like this:
[ApiVersion("1")]
[Route("api/v{version:apiVersion}/values")]
public class ValuesController : Controller
and I added to Startup : services.AddApiVersioning();. It break routing completely, now for each request i get response "Default", when i remove last app.Use, routing works fine for values controller, but i don't have default middleware. Do you know how i can get responses the same as prevously, but with ApiVersioning?
Try this.
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/foo")]
public class FooController {}
and in Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
});
}
I have tried and it worked for me.
Related
for below code I want to do setup in ConfigureTestServices so that this particular attribute gets disabled while running the test.
`public class AuthorizePermissionAttribute : TypeFilterAttribute
{
public AuthorizePermissionAttribute(Type type, params string[] permissions) : base(typeof(AuthorizePermissionPreferenceFilter<>).MakeGenericType(type))
{
Arguments = new object[] { permissions };
}
}
public class AuthorizePermissionPreFilter<T> : AuthorizePermissionFilter, IAsyncAuthorizationFilter where T : IPrefScope
{
public AuthorizePermissionPrefFilter(
IRequestContext context,
IPermissionProvider permissionProvider,
ILogger<AuthorizePermissionPreFilter<T>> logger,
IOptionsSnapshot<CoreAppConfiguration> config,
string[] permissions) : base(context, permissionProvider, logger, config, permissions)
{
}
public new async Task OnAuthorizationAsync(AuthorizationFilterContext authContext)
{
await base.OnAuthorizationAsync(authContext);
}
}`
This has been added through middleware app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
I tried using MOQ, Moq.AutoMock with xunit but nothing helped
According to this Microsoft document you should be able to apply attribute like [RequiredScope("SomeScopeName")] to either controller level or action level to protect the API. But when I try it in my API, it doesn't seem to have any effect at all - regardless what scope name I use (I made sure I don't have the scope by that name in the token), I always get right in to the API actions that I supposed to fail. But at the same time, my policy attributes, such as [Authorize(Policy = "PolicyName")], works just fine. What am I missing?
[ApiController]
[RequiredScope("AnyRandomName")]
public class MyApiController : ControllerBase
{
UPDATE
Here is my Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
services.AddControllers();
services.AddSwaggerGen(opt =>
{
opt.CustomSchemaIds(type => type.ToString() + type.GetHashCode());
});
services.Configure<HostOptions>(Configuration.GetSection(HostOptions.HOST));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
services.AddAuthentication("Bearer").AddJwtBearer(options =>
{
options.Authority = Configuration[HostOptions.IDENTITYGATEWAY];
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
services.AddTransient<gRPCServiceHelper>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseExceptionHandler("/error-local-development");
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GroupDemographicEFCore v1"));
}
else
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
and here is my API controller
[ApiController]
[Authorize]
[RequiredScope("NoSuchScope")]
public class MyApiController : ControllerBase
{
public MyApiController([NotNull] IConfiguration configuration, [NotNull] ILogger<MyApiController> logger,
[NotNull] gRPCServiceHelper helper) : base(configuration, logger, helper)
{
}
[HttpGet]
[Route("/clients/summary")]
public async Task<IActionResult> ClientsSummaryGet()
{
...
Note that I applied the attributes here on the controller level. But it makes no difference if I move them down to action level - the RequiredScope attributes always gets ignored.
UPDATE-1
I left out the AddAuthorization from my last post update, as I believe it is irrelevant to my issue here. I added it back now, with a few of the policies that I use. Once again, these policies are all working fine, and I don't see how this is relevant to the issue I have.
services.AddAuthorization(options =>
{
options.AddPolicy("OperatorCode", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("OperatorCode");
});
options.AddPolicy("OperatorCode:oprtr0", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("OperatorCode", "oprtr0");
});
options.AddPolicy("Role:User+OperatorCode:oprtr0", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("User");
policy.RequireClaim("OperatorCode", "oprtr0");
});
options.AddPolicy("Role:Admin||Role:User", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin", "User");
});
});
Here is the access_token header
Here is the body of access_token
All we need to do is add
services.AddRequiredScopeAuthorization();
For the RequireScopeAttrubute to work, which is what AddMicrosoftIdentityWebApiAuthentication does under the hood to get it to work anyway.
What you need to do is to add and configure authorization in Startup.cs like, like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("ViewReports", policy =>
policy.RequireAuthenticatedUser()
.RequireRole("Finance")
.RequireRole("Management")
);
});
The policy says that the user must be authenticated and be in both roles. In this example RequireAuthenticatedUser() is optional.
Then you can use that policy like:
[Authorize(Policy = "ViewReports")]
public IActionResult ViewReports()
{
return View();
}
To get the role claim to work, you must define what the name of your role claim is in the token, by doing this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
});
Otherwise the role might not be found, because OpenIDConnect and Microsoft have different opinion on what the claim should be called.
In the long run, using polices will gives you better and cleaner code, because if you need to change the scopes in the future, you need to update all controllers classes. With a policy , you change it in one place.
Also, according to this issue at GitHub, it says:
RequiredScopes just checks at the scp or
http://schemas.microsoft.com/identity/claims/scope claims.
This means that you might need to do some claims transformation (renaming) to get the RequiredScope to map to the scope claim in your access token.
My codes:
installing these 2 packages:
<PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="4.5.1" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.21.1" />
Startup.cs, adding code in ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");
services.AddControllers();
}
don't forget these two lines in Configure method:
app.UseAuthentication();
app.UseAuthorization();
My test controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System.Collections.Generic;
namespace WebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
[RequiredScope("User.Read")]
public class HomeController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
[HttpPost]
public string getRes() {
return "hello world";
}
}
}
Test result :
==============================================================
To protect an ASP.NET or ASP.NET Core web API, you must add the
[Authorize] attribute to one of the following items:
The controller itself if you want all controller actions to be
protected The individual controller action for your API
According to this section's example,
adding [Authorize] before the line [RequiredScope("AnyRandomName")] ?
[ApiController]
[Authorize]
[RequiredScope("AnyRandomName")]
public class MyApiController : ControllerBase
{
In a REST API project dotnet Core 5.0, I have two controllers: MyAController and MyBController in separate classes. They derive from MediatorApiController that derives from ControllerBase. They are mainly identical except for the entity they manage (CRUD). When I type https://localhost:5001/api it goes to the [HttpGet("/{lang}")] corresponding method in MyAController and display the objects {lang}='api' ??.
If I type https://localhost:5001/api/MyA/1 or https://localhost:5001/api/MyB/1 I receive an error
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints
MyAController.Get
MyBController.Get ???
Can anyone help ?
I did use edi.routedebugger, but it does not get to it (error 500), if I display the controllers they are all shown properly.
public class AController : MediatorApiController
public class BController : MediatorApiController
public class MediatorApiController : ControllerBase
[Route("api/[controller]")]
[ApiController]
public class MyAController : MediatorApiController
{
[HttpGet("/{lang}")]
public async Task<ActionResult<List<AlimentDto>>> GetAll(string lang)
{
return await Mediator.Send(new GetAllAlimentsQuery(lang));
}
[HttpGet("/{id}/{lang}")]
public async Task<ActionResult<AlimentDto>> Get(int id, string lang)
{
return await Mediator.Send(new GetAlimentByIdQuery { Id = id, Lang = lang });
}
}
startup.cs file:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwaggerUi3();
app.UseRouteDebugger();
}
//app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseOpenApi();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Try to remove "/", make it
[HttpGet("{lang}")]
public async Task<ActionResult<List<AlimentDto>>> GetAll(string lang)
for lang=en your url should be
../api/MyAControlle/en
But I prefer and recomend to use the full route attribute
[Route("~/api/MyAControlle/GetAll/{lang}]
public async Task<ActionResult<List<AlimentDto>>> GetAll(string lang)
Swagger page is not loading when I have POST and PUT method in the same API controller. If I remove or comment either one from the controller it is working fine.
Is there any way to use both in the same controller.
Swagger page
Below is my code snippets,
Controller:
[HttpGet]
public async Task<ActionResult<List<Activity>>> List()
{
return await _mediator.Send(new List.Query());
}
[HttpGet("{id}")]
public async Task<ActionResult<Activity>> Details(Guid id)
{
return await _mediator.Send(new Details.Query { Id = id });
}
[HttpPost]
public async Task<ActionResult<Unit>> Create(Create.Command command)
{
return await _mediator.Send(command);
}
[HttpPut("{id}")]
public async Task<ActionResult<Unit>> Edit(Guid id, Edit.Command command)
{
command.Id = id;
return await _mediator.Send(command);
}
[HttpDelete("{id}")]
public async Task<ActionResult<Unit>> Remove(Guid id)
{
return await _mediator.Send(new Remove.Command { Id = id });
}
Swagger configure in Startup
//Included swagger
services.AddSwaggerGen(options => options.SwaggerDoc(name: "v1", new OpenApiInfo { Title = "Reactivities", Version = "v1" }));
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(x => { x.SwaggerEndpoint("/swagger/v1/swagger.json", "Reactivities - V1"); });
Am I missing something? Please advice.
I am trying to add a new ActionModel for a ControllerModel in an implementation of IControllerModelConvention, but I cannot find any documentation or examples of how this model system works or how to do this correctly. I am able to add a new ActionModel easily enough, but it is not routable once the application is running:
var action = new ActionModel(method, new object[] { new HttpGetAttribute("/test") })
{
Controller = controller,
ActionName = "test"
};
controller.Actions.Add(action);
It seems I need to add a selector to the action, perhaps other properties as well, but I haven't been able to find one that exposes this action. Also unsure if my attributes are correct/redundant. Ultimately I would like to add multiple actions that do not map 1:1 to the methods in the controller.
I've made it work similiar to your approach. Maybe this can help you:
Controller
public class TestController : Controller
{
public IActionResult Index()
{
return Ok(new string[] { "Hello", "World" });
}
}
Model Convention
public class TestControllerModelConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
Type controllerType = typeof(TestController);
MethodInfo indexAction = controllerType.GetMethod("Index");
var testAction = new ActionModel(indexAction, new[] { new HttpGetAttribute("/test") })
{
ActionName = "Index",
Controller = controller
};
controller.Actions.Add(testAction);
}
}
Startup
public void ConfigureServices(IServiceCollection services)
{
// other initialitation stuff
services.AddMvc(options =>
{
options.Conventions.Add(new TestControllerModelConvention());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Now when I start the application and browse "/test" it will hit the controller action.