A day with .Net

My day to day experince in .net

Integrate azure key vault secrets to your Asp.net core app

Posted by vivekcek on January 2, 2021

Provision an azure key vault resource in azure and ensure service identity is configured in access policy.

Add below dependencies.

Azure.Security.KeyVault.Secrets;
Azure.Identity;
Azure.Extensions.AspNetCore.Configuration.Secrets;

Write below code in Program.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, config) =>
        {
            if (context.HostingEnvironment.IsProduction())
            {
                var builtConfig = config.Build();
                var secretClient = new SecretClient(new Uri($"https://{builtConfig["KeyVaultName"]}.vault.azure.net/"),
                                                         new DefaultAzureCredential());
                config.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());


            }
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

Posted in Asp.net, ASP.NET Core, Azure, Azure key vault | Leave a Comment »

Validate appsettings configuration values at startup in ASP.NET Core – IStartupFilter

Posted by vivekcek on January 2, 2021

We can validate the configuration settings in appsettings.json during the startup of an asp.net core application. The advantage is your application won’t start, In case if a setting is missing or in bad format.

First we need to use the concept of strongly typed configuration in asp.net core.

My configuration is like below.

{
 "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MyApi": {
    "MyUrl": "http://example.com/url",
    "Name": "Vivek",
    "Notify": true
  }
}

For the above configuration, i will create a class like below.

public class MyApiSettings
{
    public string MyUrl { get; set; }
    public string Name { get; set; }
    public bool Notify { get; set; }
}

Register the class in startup. So that the properties in json file is bind to the above class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
 
    services.Configure<MyApiSettings>(Configuration.GetSection("MyApi")); 

    services.AddSingleton(resolver => 
        resolver.GetRequiredService<IOptions<MyApiSettings>>().Value);
}

Now you can use this settings in your controller as below.

public class MyController : Controller
{
    private readonly MyApiSettings _myApiSettings;
    public MyController(MyApiSettings settings)
    {
        _myApiSettings = settings;
    }

    public object Get()
    {
        return _myApiSettings;
    }
}

Now how can we validate the properties?. What will happen if “MyUrl” is null or it’s not in Url format.

Create an interface.

public interface IValidatable
{
    void Validate();
}

Now create a settings validator by extending IStartUpFilter.

public class SettingValidationStartupFilter : IStartupFilter
{
    readonly IEnumerable<IValidatable> _validatableObjects;
    public SettingValidationStartupFilter(IEnumerable<IValidatable> validatableObjects)
    {
        _validatableObjects = validatableObjects;
    }

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        foreach (var validatableObject in _validatableObjects)
        {
            validatableObject.Validate();
        }

        return next;
    }
}

Now decorate our strongly typed class with data annotations.

public class MyApiSettings : IValidatable
{
    [Required, Url]
    public string MyUrl { get; set; }
    [Required]
    public string Name { get; set; }
    public bool Notify { get; set; }

    public void Validate()
    {
        Validator.ValidateObject(this, new ValidationContext(this), validateAllProperties: true);
    }
}

The final step is register the startup filter.

public void ConfigureServices(IServiceCollection services)  
{
    services.AddMvc();

    services.AddTransient<IStartupFilter, SettingValidationStartupFilter>()
    services.Configure<MyApiSettings>(Configuration.GetSection("MyApi")); 

    services.AddSingleton(resolver => 
        resolver.GetRequiredService<IOptions<MyApiSettings>>().Value);

    // Register as an IValidatable
    services.AddSingleton<IValidatable>(resolver => 
        resolver.GetRequiredService<IOptions<MyApiSettings>>().Value);
}

Now if any value is missing exception will be thrown at startup.

Posted in ASP.NET Core | Leave a Comment »

SwaggerUI Client Credentials Authentication not working, token_endpoint_auth_method=client_secret_post vs client_secret_basic

Posted by vivekcek on December 30, 2020

You can configure swaggerUI to generate token via Authorization code flow or Client Credentials grant type. In this post i will show you “How to use Client Credentials flow with SwaggerUI”. Please note that at the time of writing this blog post, SwaggerUI expect your authentication endpoint’s “token_endpoint_auth_method” property should be be “client_secret_basic”. In case if it is “client_secret_post” SwaggerUI authentication don’t work.

What is the difference between “client_secret_post” and “client_secret_basic”?.

If your token endpoint method is “client_secret_post”, You need to send ClientId , ClientSecret , GrantType via body of the http request with Url Encoded.

But if your token endpoint method is “client_secret_basic”. Send Base 64 encoded ClientId and ClientSecret via Authorization header(SwaggerUI use this method). GrantType and Scope can be send via http request body with Url encoded.

How to configure Client Credential flow in Asp.Net core.

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.OAuth2,
                    Flows = new OpenApiOAuthFlows
                    {
                        ClientCredentials = new OpenApiOAuthFlow
                        {
                            TokenUrl = new Uri(Configuration["Url"]),
                            Scopes = new Dictionary<string, string>
                            {
                                {"read:message", "openid"}
                            }
                        }


                    }
                });

            });

When you run your API and hit on authorize button, a popup like below will be opened.

Posted in ASP.NET Core, SwaggerUI | Tagged: | Leave a Comment »

Secure way to validate JWT signed by asymmetric key using JWKS_URI(RFC8414) or Introspection(RFC 7762) endpoint or both – Asp.net core ,Forgerock, Azure AD, WSO2, IdentityServer4, Auth0, Okta

Posted by vivekcek on December 26, 2020

Most of the authorization servers which support Oauth, Like Azure AD, IdentityServer4 , ForgeRock, WSO2 allow us to sign the JWT with a public key encryption algorithm like RS 256. The advantage of using a public key(asymmetric) algorithm is you can encrypt JWT with a private key and decode it with a public key. Here you don’t need to share your private key to your clients to decode JWT.

There are basically 4 methods to validate a JWT token at your resource server.

  1. You share your public key to resource server. So that they can use that public key to decrypt JWT and validate it.
  2. The disadvantage of above method is your resource servers need to hard-code the public key. Also what if you need to rotate your keys weekly. To resolve this problem all of the Oauth providers expose a Metadata Uri as per RFC8414. This Uri is called JWKS_URI and which expose a set of public keys used by your authorization server. Your resource server need to get public keys from this url to validate the JWT. Asp.net core doesn’t support this in it’s identity model, so you need to write your own code.
  3. What if your JWT token is revoked?. Above two methods can’t detect whether your token is revoked or not. So Oauth support an Introspection endpoint (RFC 7762). You can send your token to this endpoint and it will give a response valid or not valid. This endpoint is protected one and you need to use client-id and secret .
  4. Using a private key to sign and validate JWT. This is the least preferred method.

Method 1 & 3 is directly supported by Asp.net core. So we can look both first and the 2nd method last.

  1. Hard-coded public key.
        public void ConfigureServices(IServiceCollection services) {
            services.AddControllers();
          
            /*
             * We'll use a public key to validate if the token was signed
             * with the corresponding private key.
             */
            services.AddSingleton<RsaSecurityKey>(provider => {
                // It's required to register the RSA key with depedency injection.
                // If you don't do this, the RSA instance will be prematurely disposed.
                
                RSA rsa = RSA.Create();
                rsa.ImportRSAPublicKey(
                    source: Convert.FromBase64String(configuration["PublicKey"]),
                    bytesRead: out int _
                );
                
                return new RsaSecurityKey(rsa);
            });
            services.AddAuthentication()
                .AddJwtBearer("Asymmetric", options => {
                    SecurityKey rsa = services.BuildServiceProvider().GetRequiredService<RsaSecurityKey>();
                    
                    options.IncludeErrorDetails = true; // <- great for debugging

                    // Configure the actual Bearer validation
                    options.TokenValidationParameters = new TokenValidationParameters {
                        IssuerSigningKey = rsa,
                        ValidAudience = "jwt-test",
                        ValidIssuer = "jwt-test",
                        RequireSignedTokens = true,
                        RequireExpirationTime = true, // <- JWTs are required to have "exp" property set
                        ValidateLifetime = true, // <- the "exp" will be validated
                        ValidateAudience = true,
                        ValidateIssuer = true,
                    };
                });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
            app.UseDeveloperExceptionPage();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization(); // <- allows the use of [Authorize] on controllers and actions

            app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); });
        }

2. Introspection endpoint.

Install this library https://github.com/IdentityModel/IdentityModel.AspNetCore.OAuth2Introspection

services.AddAuthentication(OAuth2IntrospectionDefaults.AuthenticationScheme)
    .AddOAuth2Introspection(options =>
    {
        options.Authority = "https://base_address_of_token_service";
        options.IntrospectionEndpoint = "";
        options.ClientId = "client_id_for_introspection_endpoint";
        options.ClientSecret = "client_secret_for_introspection_endpoint";
    });

4. Hard-coded private key.

        public void ConfigureServices(IServiceCollection services) {
            services.AddControllers();

            /*
             * Configure validation of regular JWT signed with a symmetric key
             */
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // Set default to 'Bearer'
                .AddJwtBearer(options => { // Configure how the Bearer token is validated
                    var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["privatekey"]));

                    options.IncludeErrorDetails = true; // <- great for debugging

                    // Configure the actual Bearer validation
                    options.TokenValidationParameters = new TokenValidationParameters {
                        IssuerSigningKey = symmetricKey,
                        ValidAudience = "jwt-test",
                        ValidIssuer = "jwt-test",
                        RequireSignedTokens = true,
                        RequireExpirationTime = true, // <- JWTs are required to have "exp" property set
                        ValidateLifetime = true, // <- the "exp" will be validated
                        ValidateAudience = true,
                        ValidateIssuer = true,
                    };
                });
        }

3. JWKS_URI.

This method is little complex. You need to write below code.

using Microsoft.IdentityModel.Tokens;
using System;

namespace JwtExtensions
{
    public sealed class JwkList
    {
        public JwkList(JsonWebKeySet jwkTaskResult)
        {
            Jwks = jwkTaskResult;
            When = DateTime.Now;
        }

        public DateTime When { get; set; }
        public JsonWebKeySet Jwks { get; set; }
    }
}
using System;

namespace JwtExtensions
{
    public class JwkOptions
    {
        public JwkOptions(string jwksUri)
        {
            JwksUri = new Uri(jwksUri);
            Issuer = $"{JwksUri.Scheme}://{JwksUri.Authority}";
            KeepFor = TimeSpan.FromMinutes(15);
        }

        public JwkOptions(string jwksUri, TimeSpan cacheTime)
        {
            JwksUri = new Uri(jwksUri);
            Issuer = $"{JwksUri.Scheme}://{JwksUri.Authority}";
            KeepFor = cacheTime;
        }

        public JwkOptions(string jwksUri, string issuer, TimeSpan cacheTime)
        {
            JwksUri = new Uri(jwksUri);
            Issuer = issuer;
            KeepFor = cacheTime;
        }
        public string Issuer { get; private set; }
        public Uri JwksUri { get; private set; }
        public TimeSpan KeepFor { get; private set; }

    }
}
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

namespace JwtExtensions
{
    public static class JwksExtension
    {
        public static void SetJwksOptions(this JwtBearerOptions options, JwkOptions jwkOptions)
        {
            var httpClient = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler())
            {
                Timeout = options.BackchannelTimeout,
                MaxResponseContentBufferSize = 1024 * 1024 * 10 // 10 MB 
            };

            options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
                jwkOptions.JwksUri.OriginalString,
                new JwksRetriever(),
                new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata });
            options.TokenValidationParameters.ValidateAudience = false;
            options.TokenValidationParameters.ValidIssuer = jwkOptions.Issuer;
        }
    }
}
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;

namespace JwtExtensions
{
    public class JwksRetriever : IConfigurationRetriever<OpenIdConnectConfiguration>
    {
        public Task<OpenIdConnectConfiguration> GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
        {
            return GetAsync(address, retriever, cancel);
        }

        /// <summary>
        /// Retrieves a populated <see cref="OpenIdConnectConfiguration"/> given an address and an <see cref="IDocumentRetriever"/>.
        /// </summary>
        /// <param name="address">address of the jwks uri.</param>
        /// <param name="retriever">the <see cref="IDocumentRetriever"/> to use to read the jwks</param>
        /// <param name="cancel"><see cref="CancellationToken"/>.</param>
        /// <returns>A populated <see cref="OpenIdConnectConfiguration"/> instance.</returns>
        public static async Task<OpenIdConnectConfiguration> GetAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
        {
            if (string.IsNullOrWhiteSpace(address))
                throw LogHelper.LogArgumentNullException(nameof(address));

            if (retriever == null)
                throw LogHelper.LogArgumentNullException(nameof(retriever));

            var doc = await retriever.GetDocumentAsync(address, cancel).ConfigureAwait(false);
            LogHelper.LogVerbose("IDX21811: Deserializing the string: '{0}' obtained from metadata endpoint into openIdConnectConfiguration object.", doc);
            var jwks = new JsonWebKeySet(doc);
            var openIdConnectConfiguration = new OpenIdConnectConfiguration()
            {
                JsonWebKeySet = jwks,
                JwksUri = address,
            };
            foreach (var securityKey in jwks.GetSigningKeys())
                openIdConnectConfiguration.SigningKeys.Add(securityKey);

            return openIdConnectConfiguration;
        }
    }
}
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.RequireHttpsMetadata = true;
                    options.SaveToken = true;

                    // API Authenticator Endpoint
                    options.SetJwksOptions(new JwkOptions(Configuration["JwksUri"]));
                });
        }

        // 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.UseAuthentication();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

Posted in ASP.NET Core | Tagged: , , | Leave a Comment »

Improve Ubuntu Linux loading performance by using Systemd and Cockpit

Posted by vivekcek on December 12, 2020

We can use Systemd command and Cockpit to improve the load-time of Ubuntu. In the terminal issue below command and you can see which service take more time to load

systemd-analyze critical-chain

You can see that docker service take much time to startup. So if you are not using docker service you can temporarily disable it. To enable and disable services in Ubuntu, You can use a web based tool named Cockpit.(https://cockpit-project.org/running.html#ubuntu)

sudo apt-get install cockpit

Open the Cockpit in a browser http://localhost:9090/ . Login with your root privileged account, But select the check-box “Reuse my password for privileged tasks”.

Now go to services and enable or disable services with GUI.

Posted in Ubuntu | Tagged: , , , , , | Leave a Comment »

HttpClient.PostAsJsonAsync and HttpContent.ReadFromJsonAsync Json extensions

Posted by vivekcek on December 11, 2020

If you have configured your json serialization options in Startup.cs. You can use HttpClient and HttpContent’s json extension methods to send and parse http request as below.

public async Task<MyResponse> Call(MyRequest request)
{
     var response = await _httpClient.PostAsJsonAsync(Url, request);
     response.EnsureSuccessStatusCode();
     return await response.Content.ReadFromJsonAsync<MyResponse>();
 }

Posted in ASP.NET Core | Tagged: , , | Leave a Comment »

Nikola’s 5 laws of IoC:

Posted by vivekcek on December 9, 2020

  1. “Store in IoC container only services. Do not store any entities.”
  2. “Any class having more then 3 dependencies should be questioned for SRP violation”
  3. “Every dependency of the class has to be presented in a transparent manner in a class constructor.”
  4. “Every constructor of a class being resolved should not have any implementation other then accepting a set of its own dependencies.”
  5. “IoC container should be explicitly used only in Bootstrapper. Any other “IoC enabled” code (including the unit tests) should be completely agnostic about the existence of IoC container.”

Posted in Uncategorized | Leave a Comment »

System Text Json Global Json options in ASPNET Core 5

Posted by vivekcek on December 7, 2020

In this post i will show you , How to configure Json options with System.Text.Json in Asp.net core 5. Here i configured ignoring casing in Json and String to enum mapping.

        public void ConfigureServices(IServiceCollection services)
        {
 
            services.AddControllers()
                .AddJsonOptions(options =>
                {
                    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
                    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
                });
           
        } 

Posted in ASP.NET Core | Tagged: , , , | Leave a Comment »

Docker file for ASPNET CORE 5

Posted by vivekcek on November 26, 2020

In this post i am sharing the docker file to build a docker image for aspnet core 5.

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /app

# copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# copy everything else and build app
COPY . ./
RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "My.API.dll"]

Posted in Docker | Tagged: , | Leave a Comment »

Kali linux black screen in virtual box when using ubuntu as host machine

Posted by vivekcek on October 13, 2019

There are many issues that can cause black screen when you boot kali linux from virtual box.
In this blog i will explain one solution that worked for me on October-2019. I am using Virtual box 5 and 64 bit Ubuntu machine as my host machine. And Intel virtualisation is enabled in my BIOS.

1. I downloaded Kali virtual box 64 bit image from below location.

2. Then double clicked on the downloaded ova file. Which will ask you to import the VM.

3. After the import a VMDK will be created in below folder. Note down this path, we need this file.

4. So now start the machine. If you see a black screen like below. Try next steps.

5. The interesting thing i found is the imported virtual machine don’t work in Ubuntu. But you need to do the import to get virtual machine disk of kali.

6. Now you need to create a new virtual machine and create it from the virtual disk location of imported VM(Step 3). Ensure below settings in the new kali vm.

Posted in linux | Tagged: , | 2 Comments »