How to call custom authentication library method in Azure Function Startup? - .net-core

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

Related

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();
});
}
}
}

Servicestack redirect to metadata

i've the following problem: i've a web service application that uses ServiceStack. I'd like to register as base path "/api", but even if I set DefaultRedirectPath to "/api/metadata", when i start the app it won't redirect automatically (if i type "/api/metadata" all works)
Can anyone help me? Here's my code inside AppHost
public override void Configure(Container container)
{
SetConfig(new HostConfig
{
DefaultRedirectPath = "/api/metadata"
});
}
public class Startup
{
public IConfiguration Configuration { get; }
public Startup()
{
Configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseServiceStack(new AppHost
{
PathBase = "/api",
AppSettings = new NetCoreAppSettings(Configuration)
});
}
}
Thanks in advance and sorry for my english
Firstly I'd consider not using an /api PathBase which would disable the new /api route. E.g. if you didn't have a /api PathBase you would automatically be able to call a Hello API from /api/Hello.
But if you you still want to host ServiceStack at a custom /api path know that this is the path that ServiceStack will be mounted at, i.e. from where ServiceStack will be able to receive any requests.
Which also means you should just use /metadata which will redirect from where ServiceStack is mounted at, so if you had:
public class AppHost : AppHostBase
{
public AppHost() : base("MyApp", typeof(MyServices).Assembly) {}
public override void Configure(Container container)
{
SetConfig(new HostConfig {
DefaultRedirectPath = "/metadata"
});
}
}
Then calling https://localhost:5001/api will redirect to https://localhost:5001/api/metadata.
ServiceStack can only see requests from /api where it's mounted at, so if you wanted to redirect the / route to the metadata page you would need to register a custom ASP.NET Core handler to do it, e.g:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseServiceStack(new AppHost {
PathBase = "/api",
});
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapGet("/", async context =>
context.Response.Redirect("/api/metadata"));
});
}
Note: you no longer need to set NetCoreAppSettings() which is populated by default

EF Core DbContext sql connection string error (Tried Following info in MS Docs)

I have a .net Core application and I've been attempting to read from my local instance of SQL Server (2014) with Windows Authentication and continue to run into a repeat error about my connection string. I've been reviewing the MSDN docs as well as connectionstrings.com and thought I had everything configured correctly.
This is my error:
"System.ArgumentException: 'Format of the initialization string does
not conform to specification starting at index 0.'"
Which I take to mean the very start of my connection string.
I have read the other posts related to this exact issue but haven't been able to use them to find a solution.
Here is what I attempt when the error occurs:
public class HomeController : Controller
{
private ModelContext _context;
public HomeController()
{}
public IActionResult Index()
{
var viewModel = new HomeViewModel();
var optionsBuilder = new DbContextOptionsBuilder<ModelContext>();
optionsBuilder.UseSqlServer("DefaultConnection");
using (_context = new ModelContext(optionsBuilder.Options))
{
>>>>>> viewModel.List = _context.TableName.ToList(); <<<<<<<<
I have the following in my "appsettings.json" file:
"ConnectionStrings": {
"DefaultConnection": "Server=MyComputerName; Database=DBName; IntegratedSecurity=SSPI;"
},
In my "ModelContext.cs" file
public class ModelContext : DbContext
{
public ModelContext(DbContextOptions<ModelContext> options)
:base(options)
{ }
[<Table Properties>]
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("DefaultConnection");
}
And "Startup.cs" file:
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.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);
services.AddDbContext<ModelContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
Thanks for taking a look!
After much contemplation and re-review of the MS Documents site for EF Core DbContext, I found that I was attempting to implement ALL 3 methods of DbContext configuration: Constructor Argument, OnConfiguring, and dependency injection.
Decided to go with OnConfiguring to get the app moving.

EasyNetQ (AMQP) Single Application Connection In ASP.NET?

Community:
I'm struggling to figure out how to create a single AMQP connection that lives with my ASP.NET application lifecycle in ASP.NET using .NET Core 2.1. After researching, I've found lots of references to using a single AMQP connection for the whole application as they are expensive and slow to create and I was headed down the road of creating the connection using DI but it appears my approach is flawed, I can't seem to identify which interface I need to add as a singleton...
public void ConfigureServices(IServiceCollection services)
{
var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(Configuration.GetConnectionString("DefaultConnection"));
var envSQL = Environment.GetEnvironmentVariable("ASPNETCORE_SQL_SERVER");
if (envSQL != null)
sqlConnectionStringBuilder.DataSource = envSQL;
services.AddSingleton<IMessageBusService, MessageBusService>();
services.AddSingleton<EasyNetQ.IAdvancedBus, RabbitAdvancedBus>();
services.AddSingleton<EasyNetQ.IConnectionFactory, ConnectionFactoryWrapper>();
services.AddMvc();
}
Adding the above interfaces works but I get an error about ConnectionConfiguration service not being locatable. Is this the right direction or is there a more proper way to create a single application once EasyNetQ connection in ASP.NET core?
You can use AutoSubcriber in .net core
and use the sample code here.
add connection to appsettings.json
"MessageBroker": {
"ConnectionString": "host=localhost"
}
then add IBus in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IBus>(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddSingleton(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
add class AppBuilderExtension and use extension method for auto subscriber
public static class AppBuilderExtension
{
public static IApplicationBuilder UseSubscribe(this IApplicationBuilder appBuilder, string subscriptionIdPrefix, Assembly assembly)
{
var services = appBuilder.ApplicationServices.CreateScope().ServiceProvider;
var lifeTime = services.GetService<IApplicationLifetime>();
var Bus = services.GetService<IBus>();
lifeTime.ApplicationStarted.Register(() =>
{
var subscriber = new AutoSubscriber(Bus, subscriptionIdPrefix);
subscriber.Subscribe(assembly);
subscriber.SubscribeAsync(assembly);
});
lifeTime.ApplicationStopped.Register(() => Bus.Dispose());
return appBuilder;
}
}
add UseSubscribe in Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseSubscribe("ClientMessageService", Assembly.GetExecutingAssembly());
app.UseHttpsRedirection();
app.UseMvc();
}
then create Producers controller
[Route("api/[controller]")]
[ApiController]
public class ProducersController : ControllerBase
{
private readonly IBus _bus;
public ProducersController(IBus bus)
{
_bus = bus;
}
[HttpGet]
[Route("Send")]
public JsonResult Send()
{
_bus.Publish(new TextMessage { Text = "Send Message from the Producer" });
return new JsonResult("");
}
}
then create Consumers controller
[Route("api/[controller]")]
[ApiController]
public class ConsumersController : ControllerBase, IConsume<TextMessage>
{
[HttpGet]
[Route("Receive")]
public JsonResult Receive()
{
using (var bus = RabbitHutch.CreateBus("host=localhost"))
{
bus.Subscribe<TextMessage>("test", HandleTextMessage);
}
return new JsonResult("");
}
private static void HandleTextMessage(TextMessage textMessage)
{
var item = textMessage.Text;
}
public void Consume(TextMessage message)
{
// code receive message
}
}

ASP.NET 5 JSON with Azure Web App

I am trying to use custom configuration for a web app that I intent to host on Azure. The configuration should be overridable by Environment variables so that I can change it on Azure portal.
I tried with following code but it does not work - details below
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json", optional:true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<AppSettings>(Configuration);
}
In the controller,
public class HomeController : Controller
{
private IOptions<AppSettings> Configuration;
public HomeController(IOptions<AppSettings> configuration)
{
Configuration = configuration;
}
public async Task<IActionResult> Index()
{
string location = Configuration.Value.Location;
...
}
The default config.json file looks like,
{
"AppSettings": {
"Location" : "Singapore"
}
}
On Azure Portal, under app settings I have assigned value to AppSettings:Location to US and I am expecting US value in the controller.
Locally, in ConfigureServices I can see the value as Singapore but in the controller action Index it is null.
Am I missing something here?
When you are reading the config.json, you need to load the section that you want to read, you need to update your code as follow:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}

Resources