ASP.NET Core on request before started middleware - asp.net

I have an asp.net core application and I need to add elastic logging, I decided to use Serilog for it, but I need to add a Correlation id into logs messages, I can't do it within only HTTP correlation id header because I have service bus handlers that also need to have correlation id. So I did it with default asp request middleware, but still have logs without it
request finished/started logs aren't have correlation id
here is my serilog setup
hostBuilder.UseSerilog(
(_, _, loggerConfiguration) =>
{
loggerConfiguration
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Elasticsearch(ConfigureElasticSink(configuration, environment))
.ReadFrom.Configuration(configuration);
});
and request middleware
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
public CorrelationIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ICorrelationIdService correlationIdService)
{
context.Request.Headers.TryGetValue(Constants.CORRELATION_ID_HEADER_NAME, out var correlationId);
correlationIdService.Set(correlationId);
context.Request.Headers.TryAdd(Constants.CORRELATION_ID_HEADER_NAME, correlationIdService.CorrelationId);
LogContext.PushProperty(Constants.CORRELATION_ID, correlationIdService.CorrelationId);
await _next(context);
}
}
UDP
My startup file
using IM360.Logger;
using InMarket360EmailServiceWebApi.WebUI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLoggerServices();
builder.Services.AddWebUIServices();
builder.Host.UseExternalLogging();
var app = builder.Build();
app.UseExternalLogging(); //middleware being added in this extension method
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.Run();
public partial class Program { }
Logger extenstions file
public static IServiceCollection AddLoggerServices(this IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<ICorrelationIdService, CorrelationIdService>();
serviceCollection.AddHeaderPropagation(opt => opt.Headers.Add(Constants.CORRELATION_ID_HEADER_NAME));
return serviceCollection;
}
public static WebApplication UseExternalLogging(this WebApplication webApplication)
{
webApplication.UseMiddleware<CorrelationIdMiddleware>();
webApplication.UseHeaderPropagation();
return webApplication;
}
Have any ideas?

Related

How to call custom authentication library method in Azure Function Startup?

I am trying to create an Azure Function (using NET 6.0 and Azure Functions runtime 4.0) and want to use a custom authentication library method AddAzureAdAuthentication that exists in my shared library just like how I was able to use it in this .Net Core api to authenticate api endpoints:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAzureAdAuthentication(Configuration);
services.AddCorsConfiguration(Configuration);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseCors("AllowSpecificOrigin");
app.UseAuthentication();
app.UseMvc();
}
}
I have read many articles and SO posts, but none gave a working solution to this problem. This is what I tried so far -
public override void Configure(IFunctionsHostBuilder builder)
{
var executionContextOptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var currentDirectory = executionContextOptions.AppDirectory;
// Get the original configuration provider from the Azure Function
var configuration = builder.Services.BuildServiceProvider().GetService<IConfiguration>();
// Create a new IConfigurationRoot and add our configuration along with Azure's original configuration
Configuration = new ConfigurationBuilder()
.SetBasePath(currentDirectory)
.AddConfiguration(configuration) // Add the original function configuration
.AddJsonFile("local.settings.json", optional: false, reloadOnChange: true)
.Build();
// Replace the Azure Function configuration with our new one
builder.Services.AddSingleton(Configuration);
ConfigureServices(builder.Services);
}
private void ConfigureServices(IServiceCollection services)
{
services.AddAzureAdAuthentication(Configuration.GetSection("AzureAd"));
}
and my HttpTriggered Azure Function is a default function:
[FunctionName("TestADFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
When I hit the function, it runs always without authenticating. I am not sure how to make this call to UseAuthentication();. I guess that is what is missing. Or, is there any other way to authenticate and authorize my azure function using my custom auth library?
To achieve the above requirements in your Function app , Need to add nuget package called Microsoft.Identity.Web
And in startup.cs we can use the below sample code:
[assembly: FunctionsStartup(typeof(FunctionsAuthentication.Startup))]
namespace FunctionsAuthentication
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
// This is configuration from environment variables, settings.json etc.
var configuration = builder.GetContext().Configuration;
builder.Services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = "Bearer";
sharedOptions.DefaultChallengeScheme = "Bearer";
})
.AddMicrosoftIdentityWebApi(configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
}
}
}
And in local settings.json we need to provide the credentials of our Azure AD
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"AzureAd:Instance": "https://login.microsoftonline.com/",
"AzureAd:Domain": "<your_domain>",
"AzureAd:TenantId": "<your_tenant_id>",
"AzureAd:ClientId": "<client_id>",
"AzureAd:ClientSecret": "<client_secret>"
}
}
For complete setup please refer to this Blog

How to read the Azure App Configuration in Service Fabric ASP.NET Core Stateless Web API?

I need help to read the App Configuration in Service Fabric ASP.NET Core Stateless Web API. In the Normal ASP.NET Core Web API, we can use the Host CreateDefaultBuilder to read the config and use it in the Startup and other classes. If I try to inject in the Service Fabric Web API, it does not work. The Program.cs contains only the following.
private static void Main(string[] args)
{
try
{
// The ServiceManifest.XML file defines one or more service type names.
// Registering a service maps a service type name to a .NET type.
// When Service Fabric creates an instance of this service type,
// an instance of the class is created in this host process.
ServiceRuntime.RegisterServiceAsync("EmailAPIType",
context => new EmailAPI(context)).GetAwaiter().GetResult();
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(EmailAPI).Name);
// Prevents this host process from terminating so services keeps running.
Thread.Sleep(Timeout.Infinite);
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
throw;
}
}
And the startup.cs contains
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EmailAPI
{
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.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();
});
}
}
}
I tried to inject Host CreateDefaultBuilder in program.cs
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(ConnectionString)
.Select(ConfigValues).TrimKeyPrefix(qualtricsAppConfigPrefix + ":")
.UseFeatureFlags();
});
})
.UseStartup<Startup>());
I am running out of Ideas how to do. In Azure Function App we can do it in Startup, not sure how we can handle in Service Fabric ASP.NET Core Web API. Any examples please.
I have uploaded the sample project created in One Drive. Here is the link to it.
https://1drv.ms/u/s!Au2rKbF-hqWY61pykRlWRTI4DB8t?e=vz0c8z
Finally figured it out. For anyone who is interested here is it. If you have any better way to do it please let me know
public class Startup
{
private static string prefix = "";
public Startup(IConfiguration configuration)
{
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", false, false)
.AddEnvironmentVariables();
var builder = configurationBuilder.Build();
configurationBuilder.AddAzureAppConfiguration(o => AddApplicationKeysAppConfiguration(o, builder));
builder = configurationBuilder.Build();
configuration = builder;
Configuration = configuration;
}
private static void AddApplicationKeysAppConfiguration(AzureAppConfigurationOptions options, IConfigurationRoot configuration)
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
environment = string.IsNullOrWhiteSpace(environment) ? Environment.GetEnvironmentVariable("Environment") : environment;
string connectionString = "";
options.Connect(connectionString)
.Select($"{prefix}*", environment).TrimKeyPrefix(prefix + ":")
.UseFeatureFlags(flagOptions =>
{
flagOptions.Label = environment;
});
}
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.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();
});
}
}
}

Logged in user gets 401 Unauthorized error when calling Web API with [Authorize] attribute

I'm trying to build an ASP.NET Core app (.NET 5.0) with Angular 12 integrated in. Both of these are new technologies for me. I'm coming from a .NETFramework/AngularJS environment.
I thought this question might have solved my problem, but it does not.
I built the project with Individual Account authentication type. I created a Users web api controller and simply applied the [Authorize] attribute to the GetUsers() api. It works fine without the attribute, but with it, I get a 401 Unauthorized error while logged in. No roles or policies have been setup.
I would expect the user to be authenticated and be able to access this api through the browser while in an active session. I've tried Postman as well.
Here is the web api code that illustrates the [Authorize] attribute on the GetUsers api:
namespace BrochureManagement.api
{
[Route("api/private/users")]
[ApiController]
public class UserController : ControllerBase
{
// GET: api/<UserController>
[HttpGet]
[Authorize]
public object GetUsers()
{
var userService = new UserService();
return userService.GetUsers();
}
// GET api/<UserController>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/<UserController>
[HttpPost]
public void Post([FromBody] string value)
{
}
// PUT api/<UserController>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/<UserController>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
Here is the ConfigureServices and Configure methods in the Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<Role>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages()
.AddRazorRuntimeCompilation();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
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();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseAuthentication();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
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");
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
}
Also, the app is configured to use HTTPS. Not sure why this doesn't just work, but hoping someone can help shed some light.
When you use .AddApiAuthorization() in IdentityServer, you have to pass a bearer token to the API when calling it or else you will get a 401 error. This means that you will not be able to call it directly through the browser because it does not automatically add the bearer token.
Check your IdentityServer logs to see why it is denying the request. Also, check your Angular app to make sure that it is adding the bearer token to the request headers.

Are there a way to block queries that come to another server?

I made a Json web token to protect my services but now somebody has made a lot of queries on my data base using my login service. Are there a way to block queries that come to another server?
The queries or attacks to my server come from different ips and different countries.
I use angular 9 and asp.net core.
// POST: api/Login
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(UsuarioLogin usuarioLogin)
{
var _userInfo = await AutenticarUsuarioAsync(usuarioLogin.Usuario, usuarioLogin.Password);
if (_userInfo != null)
{
return Ok(new { token = GenerarTokenJWT(_userInfo) });
}
else
{
return Unauthorized();
}
}
Enable Cross-Origin Requests (CORS) in ASP.NET Core
There are three ways to enable CORS:
In middleware using a named policy or default policy.
Using endpoint routing.
With the EnableCors attribute.
CORS with named policy and middleware
public class Startup
{
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://xxxx.com");
});
});
// services.AddResponseCaching();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
// app.UseResponseCaching();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}

Get the requesting user in controller method of ASP.NET app

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.

Resources