Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security

ASP.NET Core Security Overview

4.92/5 (6 votes)
17 May 2018CPOL5 min read 22.6K  
This article provides an overview of ASP.NET Core security features.

Introduction

In an enterprise application scenario, securing application from different security threats is very important, to begin with providing secured authorization and authorization is the main part of the application. In this article, I will share basic knowledge of security features used in ASP.NET Core.

Background

In this section, I would like to brief about basic security types for the client-server application. In client-server application, two types of security are very important, one is data transport and data access.

1. Transport Security

A simple way to understand transport security is with the customer and bank scenario. example customer is the client/browser and the bank is the web server. In this scenario, it's easy to understand how the customer will do cash transactions with the bank.

Http: example - assume customer carrying money to a bank, the customer will use the different transportation, i.e., bus, train, and car facilities to reach the bank and carrying money in a transparent polythene bag. The customer will use many transportation facilities but carrying money in a polythene bag is a risk (anyone can see and steal money from your bag, there is no security provided to customer bag). Image 1

Https: example - the customer has to use the same transportation (as described above) but money is securely locked in a bag, the customer will lock the bag with key (provided by the bank) and the only bank can open this bag with bank personal key. Even if the customer lost the bag, no one can open or steal money from the bag (i.e., the bank has the personal key to open the locked bag).

Image 2

How Http Works?

Image 3

Who is Responsible for the Secured Transaction?

Client (Web server) will request to the server (Web Server) to do specific operations and the server will respond to the client request. In this scenario, both have to make sure secure communication. so it's both client and server responsibility to provide security.

How to Make a Secured Transaction?

Communication between client to server and server to the client is secured only with HTTPS (HyperText Transport Protocol Secure). As explained, the customer carries money with polythene bag is not secure as anyone can see your money and there is risk of theft. But in HTTPS, you are carrying money with security box - even though if someone steals your bag, she/he cannot open the box. HTTPS uses SSL certificates to secure the transactions are securely encrypted.

How SSL Works?

Related image

http://www.jordansphere.co.uk/how-does-ssl-work/

What is Secured in HTTPS?

Example HTTP Message:

Image 5

Example HTTPS Message:

Image 6

Who is Responsible for Encrypting and Decryption of SSL at Client Side

At the client, Browser is responsible for encryption and decryption of SSL certificate.

Image 7

The browser will encrypt and decrypt data using SSL certifications, browser makes sure data is encrypted sent via network channels. once server (i.e., IIS, Apache) receives data, the server has the responsibility to make sure the data is encrypted (secured) and responsible for sending secured response.

2. Application Security

Application security is what resources can be used in the application (Authorization) is determined by who is going to use the application (Authentication) providing application security both authorization and authentication required.

  1. Identification of User - Authentication
  2. Providing resource access to user - Authorization

Example: Consider a public library, the user can enter into the public library and use library resources (newspaper, books, videos, computers, etc.) by providing identity just to know the user information. Proving Identity is Authentication and permission to access library resource is Authorization.

ASP.NET Core Authentication

ASP.NET core authentication deals with mainly three parts:

  1. The AuthenticationMiddleware
  2. The AuthenticationSchemeOptions
  3. The AuthenticationHandler

AuthenticationMiddleware will intercept the pipeline and authenticates the request. To enable AuthenticationMiddleware, use UseAuthentication() function in the Startup class:

C#
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAuthentication();
}

This will add the AuthenticationMiddleware to the specified ApplicationBuilder. The default builder is created at initialization of the application.

C#
public class Program
   {
       public static void Main(string[] args)
       {
           BuildWebHost(args).Run();
       }

       public static IWebHost BuildWebHost(string[] args) =>
           WebHost.CreateDefaultBuilder(args)
               .UseStartup<Startup>()
               .Build();
   }

app.UseAuthentication(); will create new AuthenticationMiddleware middleware, AuthenticationMiddleware handles request early in the pipeline and validates the authentication.

One thing to remember about this feature is that unless proving Authorization, this middleware authentication interceptor will not restrict access to any of the resources.

AuthenticationSchemeOptions will validate the different options provided by the scheme it will be used by AuthenticationHandler. In custom authentication, this class can be derived to provide more options for validated different parameters based on requirements.

C#
/// <summary>
   /// Contains the options used by the <see cref="AuthenticationHandler{T}"/>.
   /// </summary>
   public class AuthenticationSchemeOptions
   {
       /// <summary>
       /// Check that the options are valid. Should throw an exception if things are not ok.
       /// </summary>
       public virtual void Validate() { }

       /// <summary>
       /// Checks that the options are valid for a specific scheme
       /// </summary>
       /// <param name="scheme">The scheme being validated.</param>
       public virtual void Validate(string scheme)
           => Validate();

       /// <summary>
       /// Gets or sets the issuer that should be used for any claims that are created
       /// </summary>
       public string ClaimsIssuer { get; set; }

       /// <summary>
       /// Instance used for events
       /// </summary>
       public object Events { get; set; }

       /// <summary>
       /// If set, will be used as the service type to get the Events instance instead of the property.
       /// </summary>
       public Type EventsType { get; set; }
   }

The below class CookieAuthenticationOptions derived from AuthenticationSchemeOptions and provided with many properties and events.

C#
/// <summary>
/// Configuration options for <see cref="CookieAuthenticationOptions"/>.
/// </summary>
public class CookieAuthenticationOptions : AuthenticationSchemeOptions
{
    public CookieAuthenticationOptions()
    {
        ExpireTimeSpan = TimeSpan.FromDays(14);
        ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
        SlidingExpiration = true;
        Events = new CookieAuthenticationEvents();
    }
    public CookieBuilder Cookie
    {
        get => _cookieBuilder;
        set => _cookieBuilder = value ?? throw new ArgumentNullException(nameof(value));
    }
    public IDataProtectionProvider DataProtectionProvider { get; set; }
    public bool SlidingExpiration { get; set; }
    public PathString LoginPath { get; set; }
    public PathString LogoutPath { get; set; }

    ...
}

While invoking AddCookie methods in the ConfigureServices to provide required option to validate:

C#
public void ConfigureServices(IServiceCollection services)
{

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 
    .AddCookie(options => 
        { 
            options.LoginPath = "/Account/LogIn"; 
            options.LogoutPath = "/Account/LogOff"; 
        });
}

At AuthenticationHandler validate or take action based on the specified parameters.

AuthenticationHandler will do the magic, it will do actual authentication. AuthenticationHandler helps to do whatever we want to do based on provided AuthenticationSchemeOptions. The 'HandleAuthenticateAsync' method will do everything for Authenticating the request.

C#
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()

AuthenticationHandler will be supported by AuthenticationResult and AuthenticationTicket.

AuthenticationResult - This is a simple class that contains the result of an Authenticate call.

C#
/// <summary>
/// Contains the result of an Authenticate call
/// </summary>
public class AuthenticateResult
{
    ...

    /// <summary>
    /// The authentication ticket.
    /// </summary>
    public AuthenticationTicket Ticket { get; protected set; }
    /// <summary>
    /// Indicates that authentication was successful.
    /// </summary>
    /// <param name="ticket">The ticket representing the authentication result.</param>
    /// <returns>The result.</returns>
    public static AuthenticateResult Success(AuthenticationTicket ticket)
    {
        if (ticket == null)
        {
            throw new ArgumentNullException(nameof(ticket));
        }
        return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
    }
    /// <summary>
    /// Indicates that there was a failure during authentication.
    /// </summary>
    /// <param name="failure">The failure exception.</param>
    /// <returns>The result.</returns>
    public static AuthenticateResult Fail(Exception failure)
    {
        return new AuthenticateResult() { Failure = failure };
    }
    /// <summary>
    /// Indicates that there was no information returned for this authentication scheme.
    /// </summary>
    /// <returns>The result.</returns>
    public static AuthenticateResult NoResult()
    {
        return new AuthenticateResult() { None = true };
    }
    ...
}

HandleAuthenticateAsync will return AuthenticationResult object - this result will indicate the Authentication Result. This result class is supported by AuthenticationTicket.

AuthenticationTicket is the successful result of the AuthenticationResult object. Without AuthenticationTicket, the Authentication cannot be successful.

C#
/// <summary>
/// Contains user identity information as well as additional authentication state.
/// </summary>
public class AuthenticationTicket
{
    /// <summary>
    /// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
    /// </summary>
    /// <param name="principal">the <see cref="ClaimsPrincipal"/>
    /// that represents the authenticated user.</param>
    /// <param name="properties">additional properties that can be consumed
    /// by the user or runtime.</param>
    /// <param name="authenticationScheme">the authentication middleware
    /// that was responsible for this ticket.</param>
    public AuthenticationTicket(ClaimsPrincipal principal,
           AuthenticationProperties properties, string authenticationScheme)
    {
        if (principal == null)
        {
            throw new ArgumentNullException(nameof(principal));
        }

        AuthenticationScheme = authenticationScheme;
        Principal = principal;
        Properties = properties ?? new AuthenticationProperties();
    }
    ...
}

AuthenticationHandler can be derived and validated different parameters based on different requirements, just we look into CookieAuthenticationHandler:

C#
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
    var result = await EnsureCookieTicket();
    if (!result.Succeeded)
    {
        return result;
    }

    var context = new CookieValidatePrincipalContext(Context, Scheme, Options, result.Ticket);
    await Events.ValidatePrincipal(context);

    if (context.Principal == null)
    {
        return AuthenticateResult.Fail("No principal.");
    }

    if (context.ShouldRenew)
    {
        RequestRefresh(result.Ticket);
    }

    return AuthenticateResult.Success(new AuthenticationTicket
                   (context.Principal, context.Properties, Scheme.Name));
}

This handler will call EnsureCookieTicket() and returns the AuthenticateResult based on condition check.

C#
private Task<AuthenticateResult> EnsureCookieTicket()
{
    // We only need to read the ticket once
    if (_readCookieTask == null)
    {
        _readCookieTask = ReadCookieTicket();
    }
    return _readCookieTask;
}
private async Task<AuthenticateResult> ReadCookieTicket()
{
    var cookie = Options.CookieManager.GetRequestCookie(Context, Options.Cookie.Name);
    if (string.IsNullOrEmpty(cookie))
    {
        return AuthenticateResult.NoResult();
    }

    var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding());
    if (ticket == null)
    {
        return AuthenticateResult.Fail("Unprotect ticket failed");
    }

    if (Options.SessionStore != null)
    {
        var claim = ticket.Principal.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
        if (claim == null)
        {
            return AuthenticateResult.Fail("SessionId missing");
        }
        _sessionKey = claim.Value;
        ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
        if (ticket == null)
        {
            return AuthenticateResult.Fail("Identity missing in session store");
        }
    }

    var currentUtc = Clock.UtcNow;
    var expiresUtc = ticket.Properties.ExpiresUtc;

    if (expiresUtc != null && expiresUtc.Value < currentUtc)
    {
        if (Options.SessionStore != null)
        {
            await Options.SessionStore.RemoveAsync(_sessionKey);
        }
        return AuthenticateResult.Fail("Ticket expired");
    }

    CheckForRefresh(ticket);

    // Finally we have a valid ticket
    return AuthenticateResult.Success(ticket);
}

In Cookie authentication, every request has to go through CookieAuthenticationHandler and validated cookie data and AuthenticateResult will indicate the cookie validation result.

In ASP.NET core, the complete authentication process is pretty simple and easy to provide custom authentication feature.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)