Create a component (service) with the extended principal in ASP.NET

It is a common in enterprise projects, to build components that require a security context. For example, your component Device implements an interface IDevice, which defines how to deal with some hardware device. Assume you are implementing a hardware box which will host the Device component. It is not important in this context which operations are defined on the interface. However it is required that the component is running under some security context.

The user opens the web application running on the box and enters its credentials.

Then user tries to execute some command which requires the Device component. Every user context will get its own instance of the Device component.

Usr1->Device1->DoSomething();
Usr2->Device2->DoSomething();

When implementing web applications in the ASP.NET component will be instantiated by dependency injection framework.Following code snippet shows a typical constructor of a such component.

    public AzureStorageProvider(AzureDeviceConfig config, 
    ILogger<Device> logger, 
    IPrincipal principal)
    {
            this.principal = principal;
            this.logger = logger;
    }

    public Task<object> DoSomething(object input)
    {
        // ...
    }

The constructor of the component receive by DI mechanism the instance of the principal. The principal typically contains one or more identities of type ClaimsIdentity with all their claims & co. With this principal. What and how the operation DoSomething() does use the principal is not of interest in this article.

The question here is how to obtain the instance of the principal?

When adding AAD authentication in ASP.NET, you might use connected service.



If you add the connected service, please do that on the very beginning of the project. Otherwise, the service might damage you existing configuration in the service.cs file. If you have to add the AAD support as a "connected service" please first backup Startup.cs.

AAD will add following class to your project:

 public static class AzureAdAuthenticationBuilderExtensions
 {
         . . .
         private class ConfigureAzureOptions : 
         IConfigureNamedOptions<OpenIdConnectOptions>
        {        
            public void Configure(string name, OpenIdConnectOptions options)
            {
                options.ClientId = azureOptions.ClientId;
                // ...
                
                // This handler is optional one. This is the place where you can 
                // minipulate the token.
                options.Events.OnTokenValidated = (ctx) =>
                {
                        . . .
                        // Here you can add new identity with some custom claims 
                        // if required.
                         ctx.Principal.AddIdentity(new ClaimsIdentity(claims,
                         "productidentification"));

                         return Task.CompletedTask;
                }
            }
        }

Finally, the IDevice service will be materialized in the Startup.cs file.


 public void ConfigureServices(IServiceCollection services)
 {
    . . .
    cfg = Configuration.GetSection(nameof(DeviceConfig));
    AzureStorageProviderConfig cfg = new DeviceConfig();
    cfg.Bind(cfg);

    services.AddSingleton<DeviceConfig>(cfg);
    
    services.AddTransient(typeof(IModelStore), provider =>
    {
        var storageProv = new Device(provider.GetService<DeviceConfig>(),
        provider.GetService<ILoggerFactory>().CreateLogger<Device>(),
        provider.GetService<IHttpContextAccessor>().HttpContext.User);

        return storageProv;
        });
    }
    . . .

The code shown above demonstrates how to implement the simple factory, which creates the instance of the component (in ASP.NET called service). It demonstrates how to grab the configuration from the configuration system (typically appsetting.json or environment variables), how to create the logger and how to get the principal.