How to get route data from Identity Server 4 endpoints - asp.net-core

I have a ResponseTimeMiddleware.cs responsible for getting response time metrics (I am using datadog) for every request made. Which is tagged by controller and action names. However when we hit the "connect/token" endpoint, the context.GetRouteData() is null, probably because identity server is doing it behind the scenes. Is there a way I could get this information or some other unique information where I could tag with?
here's my code:
public class ResponseTimeMiddleware
{
// other code..
public Task InvokeAsync(HttpContext context)
{
var request = context.Request;
var watch = new System.Diagnostics.Stopwatch();
watch.Start();
context.Response.OnStarting(() =>
{
watch.Stop();
var routeData = context.GetRouteData();
var responseTime = watch.ElapsedMilliseconds.ToString();
var tags = new[] { $"statusCode:{context.Response.StatusCode.ToString()}", $"controller:{routeData.Values["controller"]}", $"action:{routeData.Values["action"]}" };
context.Response.Headers[ResponseHeaderResponseTime] = responseTime;
DogStatsd.Timer("response.time", responseTime, tags: tags);
return Task.CompletedTask;
});
return nextDelegate(context);
}
}
This is my Startup:
public class Startup
{
// other code..
public static void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseMiddleware<ResponseTimeMiddleware>();
app.UseMvcWithDefaultRoute();
app.UseStaticFiles();
app.UseEndpointRouting();
app.UseCookiePolicy();
app.UseCors("CorsPolicy");
app.UseIdentityServer();
// This method gets called by the runtime. Use this method to add services to the container.
public async void ConfigureServices(IServiceCollection services)
{
services.AddDataDogStatsd(Configuration, "identity");
// other code
}
}

Use context.Request.Path conditionally if your routeData is null. It is the closest I can think of since Identity Server 4 middleware has internal routing logic for the standard OAuth protocol routes.

Related

Rewrite URL in ASP.Net Core Razor Pages (not MVC)

By seo friendly, I am trying to use the rewrite middleware to rewrite an url to a seo friendly url, but I am not successful.
As an example, what I want to do is rewrite the url https://example.com/1 to https://example.com/test-1 and the url https://example.com/1/2 to https://example.com/test-1/test-2.
I leave the Startup and ChangeURL classes that I have made.
Startup.cs:
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.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var options = new RewriteOptions();
options.Rules.Add(new ChangeUrl());
app.UseRewriter(options);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
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.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
ChangeURL.cs:
public class ChangeUrl : IRule
{
public void ApplyRule(RewriteContext context)
{
var request = context.HttpContext.Request;
Match m1 = Regex.Match(request.Path.Value, #"^/(\d+)");
Match m2 = Regex.Match(request.Path.Value, #"^/(\d+)/(\d+)");
if (m1.Success)
{
request.Path = "/test-1";
}
else if (m2.Success)
{
request.Path = "/test-1/test-2";
}
context.Result = RuleResult.ContinueRules;
return;
}
}
I appreciate all the help you can give me.
As far as I know, asp.net core contains the url rewrite module which is used to url rewrite it.
More details, you could refer to below codes:
// Add below codes into the Configure method
var options = new RewriteOptions()
.AddRewrite(#"^(\d+)/(\d+)", "test-$1/test-$2",
skipRemainingRules: true)
.AddRewrite(#"^(\d+)", "test-$1",
skipRemainingRules: true);
app.UseRewriter(options);
More details, you could refer to this artcle.
public void ApplyRule(RewriteContext context)
{
var request = context.HttpContext.Request;
Match m1 = Regex.Match(request.Path.Value, #"^/(\d+)");
Match m2 = Regex.Match(request.Path.Value, #"^/(\d+)/(\d+)");
if (m1.Success)
{
request.Path = "/home/Privacy";
}
if (m2.Success)
{
request.Path = "/home/account";
}
context.Result = RuleResult.ContinueRules;
return;
}

.net core 5.0.2 and jwt => response 401 Unauthorized

I am following an video tutorial for identity server 4 with web api's.
And Im not sure when I went wrong.
Im getting 401 Unauthorized when I try to call api with bearer token.
In previos step, without authorization, my api worked.
This is my api controller in my TablesReach.API project:
...
namespace TablesReach.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly DataContext _context;
public UsersController(DataContext context)
{
_context = context;
}
// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
...
this is my Startup.cs of my api project:
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(opts =>
{
opts.Authority = "http://localhost:5000";
opts.RequireHttpsMetadata = false;
opts.ApiName = "TablesReachApi";
});
services.AddDbContext<DataContext>(opts => opts.UseInMemoryDatabase("UNWDb"));
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.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseAuthentication();
}
}
My other project TablesReach.IdentityServer is host on localhost:5000
and Im being able to get bearer token, so I assume that this project is quite OK.
identityServer startup.cs class:
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.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiScopes(Config.GetAllApiResources())
.AddInMemoryClients(Config.GetClients());
}
// 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();
}
//else
//{
// app.UseExceptionHandler("/Home/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.UseRouting();
//app.UseAuthorization();
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllerRoute(
// name: "default",
// pattern: "{controller=Home}/{action=Index}/{id?}");
//});
app.UseIdentityServer();
}
}
and Config.cs:
public class Config
{
public static IEnumerable<ApiScope> GetAllApiResources()
{
return new List<ApiScope>
{
new ApiScope("TablesReachApi", "Api for solution")
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "TablesReachApi" }
}
};
}
}
Note: When I remove annotation [Authorize] from my api controller I can reach my method.
For some middleware, order matters. Authentication and authorization, for example, can't go in the order that you have put them in the API. Microsoft has some clear documentation on this for you to read here..

Asp Net Core, Logic for custom request headers

I'd like to create an Asp.Net Core MVC that can deny requests that lack specific headers.
For Example:
I want to access a Controller and and only allow Requests whose Header contains a specific (custom made) Authorization Type and Token.
I've done some research on it but I could not find anything on this topic that gave me an idea on how to even start.
You could custom a middleware to check the request header type and value:
public class SimpleHeaderAuthorizationMiddleware
{
private readonly RequestDelegate _next;
public SimpleHeaderAuthorizationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Accept"];
if (!string.IsNullOrEmpty(authHeader))
{
if (authHeader == "specific_value")
{
//do your stuff....
//throw new Exception("The HTTP header value is not correct!");
context.Response.StatusCode = 403;
}
await _next(context);
}
else
{
//reject the request if do not provide Authorization header
//throw new Exception("Necessary HTTP header not present!");
context.Response.StatusCode = 401;
}
}
}
Create Middleware extension method:
public static class SimpleHeaderAuthorizationMiddlewareExtension
{
public static IApplicationBuilder UseSimpleHeaderAuthorization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<SimpleHeaderAuthorizationMiddleware>();
}
}
The following code calls the middleware from Startup.Configure:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSimpleHeaderAuthorization(); //add this...
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}

ASP.NET Core Odata Service Post not working

I implemented a ODATA Service in my ASP.NET Core application. The GET function is working fine, but I have some problems with the POST function.
If I excecute a POST the programm is excecuting the right method but I don't receive any data.
Is there anything missing in my code?
Controller:
[EnableCors]
[ODataRoutePrefix("documents")]
public class DocumentController : ODataController
{
[ODataRoute]
[EnableQuery]
public Document PushDocument([FromBody]Document doc)
{
System.Diagnostics.Debug.WriteLine("DomentID: " + doc.Id);
System.Diagnostics.Debug.WriteLine("Dokument: " + doc.RawDocument);
return doc;
}
}
Since you use [FromBody], you need to send data as Content-Type: application/json,in postman:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
services.AddMvc(options =>
{
options.EnableEndpointRouting = false;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// 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.UseHttpsRedirection();
app.UseMvc(b =>
{
b.MapODataServiceRoute("odata", "odata", GetEdmModel());
});
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Document>("Documents");
builder.EntitySet<Press>("Presses");
return builder.GetEdmModel();
}

How to return indented json content from an OData controller in asp core web api?

I can retrieve intended json result from normal WebApi using following way.
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(x=>
{
x.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
});
But I cannot find a way to output json like this when using ODataController as opposed to ControllerBase when web api is used. ODataController always sends a minified json.
public class EmployeeController : ODataController
{
[EnableQuery()]
public IActionResult Get()
{
return Ok(new BOContext().Employees.ToList());
}
}
Also, startup.cs
public class Startup
{
private static IEdmModel GetModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employee");
return builder.GetEdmModel();
}
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.AddOData();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(x=>
{
x.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
});
}
// 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
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapODataServiceRoute("odata", "odata", GetModel());
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The route is working and I am receiving correct data.
Is there a way to control and output indented json from an OData controller?
I suggest you to make the transfer with minified jsonm, but use json beutifier to show formatted json. dont do this on the data flow phase.
If you are using javascript on the front-end side. You can simple use
JSON.stringify(jsObj, null, "\t"); // stringify with tabs inserted at each level
JSON.stringify(jsObj, null, 2); // stringify with 2 spaces at each level
Not sure if this is still actual, but you can specify formatter when returning the data
// [...]
public IActionResult Get()
{
var res = Ok(_db.Employees);
res.Formatters.Add(new Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter(
new Newtonsoft.Json.JsonSerializerSettings() { Formatting = Newtonsoft.Json.Formatting.Indented },
System.Buffers.ArrayPool<char>.Create()));
return res;
}
And of course, if you want more generalized solution (or you just have a lot of code that is already written), you can create interim abstract class and inherit from that class instead of just ODataController:
public abstract class AbstractFormattedOdataController : ODataController
{
public override OkObjectResult Ok(object value)
{
var res = base.Ok(value);
res.Formatters.Add(new Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter(
new Newtonsoft.Json.JsonSerializerSettings() { Formatting = Newtonsoft.Json.Formatting.Indented },
System.Buffers.ArrayPool<char>.Create()));
return res;
}
}
// [...]
public class EmployeesController : AbstractFormattedOdataController
{
[EnableQuery()]
public IActionResult Get()
{
return Ok(new BOContext().Employees.ToList());
}
}