Friday, 16 May 2025

MSAL (Microsoft Authentication Library)

 

🔐 What is MSAL?

MSAL (Microsoft Authentication Library) is a Microsoft-supported SDK used to authenticate users and acquire tokens from:

  • Azure AD

  • Azure AD B2C

  • Microsoft Identity Platform (v2 endpoint)

It supports:

  • Authorization Code Flow (for web apps)

  • Device Code Flow

  • Username/password (ROPC – discouraged for production)

  • Confidential client (server-to-Azure API access)

  • Public client (desktop/mobile apps)


✅ MSAL is used with OpenID Connect and OAuth 2.0 (not SAML)

MSAL is not a SAML library. It's used for OpenID Connect/OAuth2-based authentication flows, usually with Azure AD or Azure B2C.


🧰 MSAL Libraries for .NET

PlatformLibraryNuGet Package
Web apps / APIsMicrosoft.Identity.WebMicrosoft.Identity.Web
General useMicrosoft.Identity.ClientMicrosoft.Identity.Client (MSAL itself)
Blazor (WASM)Microsoft.Authentication.WebAssembly.MsalBlazor-specific

🔧 Example Usage in .NET Core / .NET 8

For Web Apps (OpenID Connect):

Use Microsoft.Identity.Web:

csharp
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C")) .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftGraph() .AddInMemoryTokenCaches();

Under the hood, Microsoft.Identity.Web uses MSAL.NET to acquire and cache tokens.

For APIs (Confidential client):

csharp
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder .Create(clientId) .WithClientSecret(clientSecret) .WithAuthority(new Uri(authority)) .Build(); AuthenticationResult result = await app .AcquireTokenForClient(scopes) .ExecuteAsync();

🎯 Where MSAL Fits

ScenarioUse MSAL?
Web app login (Azure AD/B2C)✅ Yes
Call Microsoft Graph API✅ Yes
Authenticate users with SAML❌ No
Acquire tokens for Azure APIs✅ Yes
Desktop/mobile app login✅ Yes
Integrate with Okta/ADFS (SAML only)❌ No

📌 Summary

  • MSAL is for token acquisition using OAuth 2.0/OpenID Connect, not SAML.

  • Use Microsoft.Identity.Web for ASP.NET Core web apps — it simplifies MSAL integration.

  • For backend-only APIs, use Microsoft.Identity.Client directly.

  • To troubleshoot token/cookie issues, MSAL logging can be enabled via:

Saturday, 19 April 2025

Building a KeyVaultService in .NET for Secure Secret Management

////Declare Interface
public interface IKeyVaultService
{
    public string GetSecret(string secretName, bool appendPrefix = true);

}
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
public class KeyVaultService : IKeyVaultService
{
    private const string _PREFIX = "XXX";
    private readonly string _keyVaultBaseUrl;
    protected  SecretClient _keyVaultClient;
    private static readonly ConcurrentDictionary<string, string> _keyVaultSecrets =
                         new ConcurrentDictionary<string, string>();
    private readonly KeyVaultConfigAppSetting _keyVaultLocalConfigAppSetting;
    private readonly ILogger _logger;
   
    /// <summary>
    /// initialize
    /// </summary>
    /// <param name="keyVaultLocalOptions"></param>
    /// <exception cref="Exception"></exception>
    public KeyVaultService(
        IOptions<KeyVaultConfigAppSetting> keyVaultLocalOptions
        ,ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<KeyVaultService>();
        _keyVaultLocalConfigAppSetting = keyVaultLocalOptions.Value;
        _keyVaultBaseUrl = _keyVaultLocalConfigAppSetting.BaseUrl;
        _keyVaultBaseUrl = _keyVaultLocalConfigAppSetting.BaseUrl
            ?? throw new InvalidOperationException
            ("KeyVaultBaseUrl is null or not configured");

        EnsureConfigured();

    }

    /// <summary>
    /// get secret value from keyvault
    /// </summary>
    /// <param name="secretName"></param>
    /// <param name="appendPrefix"></param>
    /// <returns></returns>
    public string GetSecret(string secretName, bool appendPrefix = true)
    {
        try
        {
            EnsureConfigured();
       
            if (appendPrefix && !secretName.StartsWith(_PREFIX))
                secretName = $"{_PREFIX}{secretName}";
            _keyVaultSecrets.TryGetValue(secretName, out var secretValue);

            if (string.IsNullOrEmpty(secretValue))
            {
                var secretBundle = _keyVaultClient.GetSecret(secretName);
                secretValue = secretBundle.Value?.Value;

                if (!string.IsNullOrEmpty(secretValue))
                    _keyVaultSecrets.TryAdd(secretName, secretValue);
            }

            return secretValue;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Keyvalut Error:{0} {1}", GetType()?.Name, ex.Message);
            throw new InvalidOperationException
            ("An error occurred while accessing the Keyvault.", ex);
        }

    }

    private void EnsureConfigured()
    {
        if (_keyVaultClient == null)
#if DEBUG
//Create azure key-vault instance using Application client id and client secret.
            ConfigureWithClientCredentials();
#else
            ConfigureAzureCredentials();
#endif
    }

    private void ConfigureWithClientCredentials()
    {


        var tenantId = _keyVaultLocalConfigAppSetting.TenantId;
        var sharedSecretKey = _keyVaultLocalConfigAppSetting.SharedSecretKey;
        var sharedSecretAppId = _keyVaultLocalConfigAppSetting.AppId;

        // If Shared Secret is present and not "Disabled",
        // use the shared secret (typically for local development).
        if (!string.IsNullOrWhiteSpace(sharedSecretKey)
        && sharedSecretKey != "Disabled")
        {
           
                ClientSecretCredential clientCredential =
                            new ClientSecretCredential
                            (tenantId, sharedSecretAppId, sharedSecretKey);
                _keyVaultClient = new SecretClient
                    (vaultUri: new Uri(_keyVaultBaseUrl), credential: clientCredential);
           
        }
        else
        {
            //fallback to the default credential authentication
            _keyVaultClient = new SecretClient(
                vaultUri: new Uri(_keyVaultBaseUrl)
                , credential: new DefaultAzureCredential());
        }

    }

    private void ConfigureAzureCredentials()
    {
        try
        {
            _keyVaultClient = new SecretClient(
                vaultUri: new Uri(_keyVaultBaseUrl)
                , credential: new DefaultAzureCredential());
        }
        catch (Exception ex)
        {
            _logger.LogError
            (ex,"Keyvalut Error: {0}  - {1}", GetType()?.Name, ex.Message);
            throw;
        }
    }
}

Middleware in .Net Core

Middleware is an software used to handle web request pipeline. 

Describe in simple term means it's organize the request and response using delegates and pipeline.

It is an decision maker to pass the next request to particular component.

Example:

3-tier Architecture (API, Business Layer, Data Layer)

we will receive n-number request at the time middleware anonymously  handle the request in pipeline to execute API --> Business Layer --> Data Layer and response vise versa.

Middleware Roles and Responsibility

    1.Chooses whether to pass the request to the next component in the pipeline.

    2.Can perform work before and after the next component in the pipeline.

Request delegates are used to build the request pipeline. The request delegates handle each HTTP request.


Request delegates are configured using RunMap, and Use extension methods. An individual request delegate can be specified in-line as an anonymous method (called in-line middleware), or it can be defined in a reusable class. These reusable classes and in-line anonymous methods are middleware, also called middleware components. Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the pipeline. When a middleware short-circuits, it's called a terminal middleware because it prevents further middleware from processing the request.

Built-in middleware
MiddlewareDescription
HTTP Method OverrideAllows an incoming POST request to override the method.
HTTPS RedirectionRedirect all HTTP requests to HTTPS.
HTTP Strict Transport Security (HSTS)Security enhancement middleware that adds a special response header.

middleware workflow






Friday, 4 April 2025

Elegant HTTP Response Parsing in .NET

/// <summary>
/// Extension methods for parsing API responses into OperationalResult<T>.
/// </summary>
public static class ApiResponseParser
{
    /// <summary>
    /// Parses the HTTP response and returns a standardized OperationalResult<T>.
    /// Handles success, primitive types, and structured error responses.
    /// </summary>
    /// <typeparam name="T">The expected type of the result.</typeparam>
    /// <param name="apiResponse">The HTTP response message.</param>
    /// <returns>An OperationalResult containing the parsed result or an error.
    /// </returns>
    public static async Task<OperationalResult<T>> GetParsedApiResponse<T>
                        (this HttpResponseMessage apiResponse)
    {
        var responseBody = await apiResponse.Content.ReadAsStringAsync();

        if (ValidHttpStatusCodes.Contains(apiResponse.StatusCode)
                && apiResponse.StatusCode != HttpStatusCode.BadRequest)
        {
            var type = typeof(T);

            if (string.IsNullOrEmpty(responseBody))
            {
                return OperationalResult<T>.Successed();
            }

            if (type.IsPrimitive)
            {
                var primitiveValue = (T)Convert.ChangeType(apiResponse.Content,
                                     typeof(T));
                return OperationalResult<T>.Successed(primitiveValue);
            }

            var data = responseBody.JsonDeserializeObject<T>();
            return OperationalResult<T>.Successed(data);
        }
        else
        {
            var apiError = responseBody.JsonDeserializeObjectTryParse<ApiError>();

            if (apiError != null)
            {
                return OperationalResult<T>.Failed(
                    (ResponseStatusType)apiError.Code, apiError.Message);
            }

            return OperationalResult<T>.Failed(
                (ResponseStatusType)apiResponse.StatusCode,
                new ErrorDto($"Error: {apiResponse.StatusCode}"
                , apiResponse.ReasonPhrase)
            );
        }
    }

    /// <summary>
    /// A list of valid HTTP status codes considered as successful responses.
    /// </summary>
    public static readonly IEnumerable<HttpStatusCode> ValidHttpStatusCodes =
                            new List<HttpStatusCode>
                            {
                                HttpStatusCode.OK,
                                HttpStatusCode.Created,
                                HttpStatusCode.NoContent
                            };
}

Sunday, 26 January 2025

Apply Migration and DB schema if not exist while run local

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
  {
      if (env.IsDevelopment())
      {
          app.UseDeveloperExceptionPage();

          // Ensure the database is created and apply migrations if it is empty
          using (var scope = app.ApplicationServices.CreateScope())
          {
              var dbContext = scope.ServiceProvider.GetRequiredService<DbContext>();


              // Retrieve the IRelationalDatabaseCreator service from the service provider
              var databaseCreator = dbContext.Database
                                .GetService<IRelationalDatabaseCreator>();

              // Check if the database exists, and create it if it doesn't
              // Create the database If SQL server is local.Otherwise don't apply migration and database creation
              if (!databaseCreator.Exists())
              {
                      databaseCreator.Create();
                  // Check if the database has any tables
                  if (!databaseCreator.HasTables())
                  {
                      dbContext.Database.Migrate();
                  }// Apply all migrations
               
              }
             
          }
      }
}

Monday, 13 January 2025

EF Core 9 Generate Code First from Existing DB

1.Install EF Core, EF core Design, EF sql server using nuget packages.

2. Open package manger console 

3. Select the project where need to place entity and DbContext

4.Run below comments with modified inputs which is highlighted.

dotnet ef dbcontext scaffold "Server={Server};Database={DBname};User Id={db user name};Password={PWD};Encrypt=True;TrustServerCertificate=True;" Microsoft.EntityFrameworkCore.SqlServer --output-dir DbEntity --context-dir DbContext --context CoreDbContext --use-database-names --force --project D:/Source_vs2022/Core.Data/Core.Data.csproj

5. Now we can see Db context and db entity



6.Migrate to Full Code-First

If you want to start using migrations with the generated context execute below in same package mager console.

  1. Add migrations:
    dotnet ef migrations add InitialCreate --project Core.Data
  2. Update the database:
    dotnet ef database update --project Core.Data --verbose
Where :
  • InitialCreate is migration name
  • Core.Data is project name which contains EF.
NOTE: 
  • In case any other error occurs like missing OnConfigure then add below code in DBContext file.
  • after migration generated remove it below code due to security reason.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseSqlServer("Server={DB-Server};Initial Catalog={DB};User ID={UserName};Password={PWD};");
    }
}


Sunday, 12 January 2025

Azure Cloud Services

Public cloud

Services are offered over the public internet and available to anyone who wants to purchase them. Cloud resources, such as servers and storage, are owned and operated by a third-party cloud service provider, and delivered over the internet.


Private cloud

A private cloud consists of computing resources used exclusively by users from one business or organization. A private cloud can be physically located at your organization's on-site (on-premises) datacenter, or it can be hosted by a third-party service provider.


Hybrid cloud

A hybrid cloud is a computing environment that combines a public cloud and a private cloud by allowing data and applications to be shared between them.

Public cloud Private cloud Hybrid cloud
No capital expenditures to scale up.
Applications can be quickly provisioned and deprovisioned.
Organizations pay only for what they use.
Hardware must be purchased for start-up and maintenance.
Organizations have complete control over resources and security.
Organizations are responsible for hardware maintenance and updates.
Provides the most flexibility.
Organizations determine where to run their applications.
Organizations control security, compliance, or legal requirements.

cloud service models
IaaS: Infrastructure-as-a-Service

This cloud service model is the closest to managing physical servers; a cloud provider will keep the hardware up-to-date, but operating system maintenance and network configuration is up to you as the cloud tenant.
For example, Azure virtual machines are fully operational virtual compute devices running in Microsoft datacenters. An advantage of this cloud service model is rapid deployment of new compute devices. Setting up a new virtual machine is considerably faster than procuring, installing, and configuring a physical server.
The most flexible cloud service. You configure and manage the hardware for your application.


PaaS: Platform-as-a-Service

This cloud service model is a managed hosting environment. The cloud provider manages the virtual machines and networking resources, and the cloud tenant deploys their applications into the managed hosting environment.
For example, Azure App Services provides a managed hosting environment where developers can upload their web applications, without having to worry about the physical hardware and software requirements.
Focus on application development. Platform management is handled by the cloud provider.


SaaS: Software-as-a-Service

In this cloud service model, the cloud provider manages all aspects of the application environment, such as virtual machines, networking resources, data storage, and applications. The cloud tenant only needs to provide their data to the application managed by the cloud provider.
For example, Microsoft Office 365 provides a fully working version of Microsoft Office that runs in the cloud. All you need to do is create your content, and Office 365 takes care of everything else.
Pay-as-you-go pricing model. Users pay for the software they use on a subscription model.



Cloud service model comparison