A day with .Net

My day to day experince in .net

Archive for December, 2020

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 »