Search code examples
oauth-2.0access-tokenwindows-authenticationbasic-authenticationasp.net-core-webapi

Secure Web Api to consumed by console Appplicatuion


I have created one Asp core web api which will be consumed by C# console application outside the organization. This console application is scheduled to run periodically . So hits on Web api will come when console application run. Please assist How Can i secure my Web Api to malware hit or unauthentic access. I can't use AD authentication as I am unable to register client application in AAD(Azure active directory) Please assist.


Solution

  • Generally speaking , there're lots ways to do that . For example , use a basic scheme authentication in which the client sends username:password with the base64-encoding . However . It's not that safe .

    I suggest you use JWT token . The authentication of Jwt scheme is dead simple :

    1. The client send a request to ask for a JWT token with client_id and client_key . (You might configure them in configuration file or database on server)
    2. Tf the client_id and client_key matches , the Server send a response with a JWT access token , maybe an additional refresh token if you like ; otherwise , send a response with a 401.
    3. The client consumes webapi with a Authorization: Bearer ${access_token} header. The server will decrypt the access_token and hit the correct action if valid.

    Here's a how-to in details:

    1. Dummy class to hold information

    To represent the client_id and client_key sent by your console , Let's create a dummy Dto class :

    public class AskForTokenRequest
    {
        public string ClientId { get; set; }
        public string ClientKey { get; set; }
    }
    

    When creating and validating Jwt token , we need information about issuer , audience , and secret keys . To hold these information , let's create another dummy class :

    public class SecurityInfo {
        public static readonly string Issuer = "xxx";
        public static readonly string[] Audiences = new[] { "yyy" };
        public static readonly string SecretKey = "!@#$%^&*()&!!!@#$%^&*()&!!!@#$%^&*()&!!!@#$%^&*()&!!!@#$%^&*()&!";
    }
    
    1. Before we move on , let's create a JwtTokenHelper to generate token :

    The JwtTokenHelper helps to validate client_id & client_key and generate Jwt Token .

    public class JwtTokenHelper
    {
        //private AppDbContext _dbContext { get; set; }
    
        //public JwtTokenHelper(AppDbContext dbContext) {
        //    this._dbContext = dbContext;
        //}
    
        public virtual bool ValidateClient(string clientId, string clientKey)
        {
            // check the client_id & clientKey with database , config file , or sth else 
            if (clientId == "your_console_client_id" && clientKey == "your_console_client_key")
                return true;
    
            return false;
        }
    
        /// construct a token
        public virtual JwtSecurityToken GenerateToken(string clientId, DateTime expiry, string audience)
        {
            ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(clientId, "jwt"));
            var token=new JwtSecurityToken
            (
                claims: identity.Claims,
                issuer: SecurityInfo.Issuer,
                audience: audience,
                expires: expiry,
                notBefore: DateTime.UtcNow,
                signingCredentials: new SigningCredentials(
                    new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityInfo.SecretKey)),
                    SecurityAlgorithms.HmacSha256
                )
            );
            return token; 
        }
    
    
        public virtual string GenerateTokenString(string clientId, DateTime expiry,string audience)
        {
            // construct a jwt token
            var token = GenerateToken(clientId,expiry,audience);
            // convert the token to string
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
            return tokenHandler.WriteToken(token);
        }
    
    }
    
    1. Configure the server to enable JwtBearer authentication :

    Add JwtTokenHelper to DI Container and Add authentication scheme of JwtBearer

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<JwtTokenHelper>();
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidIssuer = SecurityInfo.Issuer,
                    ValidAudiences = SecurityInfo.Audiences,
                    ValidateAudience = true,
                    ValidateIssuer = true,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKeys = new List<SecurityKey> {
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityInfo.SecretKey) )
                    },
                    ValidateLifetime = true, 
                    ClockSkew = TimeSpan.FromMinutes(60) 
                };
            });
    
        services.AddMvc();
    }
    

    Don't forget to add app.UseAuthentication(); in your Configure() method .

    1. How to use:

    Now , Create a controller to generate Jwt token

    [Route("/api/token")]
    public class TokenController : Controller
    {
        private readonly JwtTokenHelper _tokenHelper;
    
        public TokenController(JwtTokenHelper tokenHelper) {
            this._tokenHelper = tokenHelper;
        }
    
        [HttpPost]
        public IActionResult Create([FromBody] AskForTokenRequest client)
        {
            if(! this._tokenHelper.ValidateClient(client.ClientId , client.ClientKey)) 
                return new StatusCodeResult(401);
    
            DateTime expiry = DateTime.UtcNow.AddMinutes(60); // expires in 1 hour 
            var audience = "yyy";
            var access_token = this._tokenHelper.GenerateTokenString(client.ClientKey, expiry,audience);
            return new JsonResult(new { 
                access_token = access_token,
            });
        }
    }
    

    and protect you webapi with [Authorize] attribute :

    public class HomeController : Controller
    {
    
        [Authorize]
        public IActionResult GetYourWebApiMethod(){
            return new ObjectResult(new {
                Username = User.Identity.Name
            });                
        }
    }