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)
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
{
I am currently working on an ASP.NET Web app with angular as a front end. As a base, the new template present in VisualStudio 2019 for ASP.NET angular, with Individual Authentication.
This runs on dotnet core 3.0 Preview 4.
First a user is created through the register interface of the template application. Then when a request is made to a controller of the backend, I would like to get the ApplicationUser that made the request.
Is that possible? Do I need to put any sort of token in the header of the http request in the frontend? Do I need to do anything special in the backend?
Currently, the http request in the controller looks like this.
import { Component, Inject } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Router } from "#angular/router";
import { error } from 'protractor';
#Component({
selector: 'app-classes-component',
templateUrl: './classes.component.html'
})
export class ClassesComponent {
public classes: Class[];
public http: HttpClient;
public baseUrl: string;
public courseCodeValue: string;
constructor(http: HttpClient, #Inject('BASE_URL') baseUrl: string, private router: Router) {
this.http = http;
this.baseUrl = baseUrl;
this.refreshCourses();
}
public refreshCourses() {
this.http.get<Class[]>(this.baseUrl + 'api/Courses/GetCourses').subscribe(result => {
this.classes = result;
}, error => console.error(error));
}
}
The Controller looks like this:
[Authorize]
[Route("api/[controller]")]
public class CoursesController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
public CoursesController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpGet("[action]")]
public IEnumerable<CourseDto> GetCourses()
{
var user = _userManager.GetUserAsync(User).Result;
// Here the user is null
return user.Courses.Select(item => new CourseDto
{
CourseCode = item.CourseCode,
CurrentGrade = item.CurrentGrade
});
}
}
The issue is that when I try to get the user that is making the http request with the usermanager, I get null. So I was wondering if I was missing something. Like prehaps some sort of token in the header of the request? Am I doing something wrong on the controller side?
EDIT: here is the Startup.cs 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.AddMvc();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddMvc(options => options.EnableEndpointRouting = false)
.AddNewtonsoftJson();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
// 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.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseAuthentication();
app.UseIdentityServer();
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.UseAngularCliServer(npmScript: "start");
}
});
}
}
You can use "User.Identity.Name" to get the UserId of the User making the request then pass it to FindByIdAsync().
var user = await _userManager.FindByIdAsync(User.Identity.Name);
or a db hit against the UserId(User.Identity.Name);
which ever works best for you.
Please let me know if this works.
I'm trying to implement .NET Core 2.2/SignalR 1.1.0.
In startup:
public void ConfigureServices(IServiceCollection services)
services.AddSignalR();
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
It works smoothly when I apply a one-to-one example.
But I need an architectural change.
My example:
public class ChatHub : Hub
{
ResponseHandler ResponseHandler { get; set; }
public ChatHub()
{
IHubCallerClients hubCallerClients = this.Clients;
ResponseHandler = new ResponseHandler(hubCallerClients);
}
public async Task SendMessage(string user, string message)
{
IHubCallerClients hubCallerClients = this.Clients;
await ResponseHandler.R();
}
}
If I tried to get this.Clients in the constructor it is coming with null data. But if I try to take it in the method, it comes full as expected.
I should get IHubCallerClients in the contructor so that I can forward it to another Response context.
Thanks advance!
OK. I solved the problem by
public class RequestHandler : Hub
{
ResponseHandler ResponseHandler { get; set; }
public RequestHandler(IHubContext<RequestHandler> hubContext)
{
ResponseHandler = new ResponseHandler(hubContext);
}
public async Task SendMessage(string user, string message)
{
await ResponseHandler.R();
}
}
Due to the nature of .net core, context comes to constructor as dependency.
"services.AddSignalR();" we're sure to add it to Scope.
"IHubContext hubContext" In this way, we can collect the contructured object.
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.