1. Authentication and Authorization

Authentication and authorization

Authentication is the process of verifying the identity of a user by obtaining some sort of credentials and using those credentials to verify the user’s identity.

Authorization is the process of allowing an authenticated users to access the resources by checking whether the user has access rights to the system. Authorization helps you to control access rights by granting or denying specific permissions to an authenticated user.

There are a few authentication mechanisms:

  • basic authentication
  • Session-based authentication with cookies
  • OpenID/OAuth Authentication

Basic authentication

Basic authentication is a simple authentication scheme built into the HTTP protocol. The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. Flow:

  1. 1a users sends a username and a password via a standard browser login screen.
  2. The browsers encodes the string with Base64 encoding
  3. From now on, every request to the server contains the encoded string in the HTTP header (Authorization key-value field)

Basic Authentication in practice

1. Create a new empty API controller and call it e.g. BasicAuthController, and make a new action method Login:

2. public class BasicAuthController :

 ControllerBase
    {

        [HttpGet]
        public IActionResult Login([FromHeader] string authorization)
        {
            Response.Headers.Add("WWW-Authenticate", "basic");

            return Unauthorized();

        }

The method creates a new Key-Value pair in the Header of the response and sends an 401 unauthorized message back to the client.

Extra reading:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication

The most important item to understand is the scheme below:


https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication

If the server sends back the WWW-Authenticate key with Basic as value it will use the built-in authentication of the HTTP protocol and the browser will automatically popup a login screen:

If you fill in a username and password you will see a request will send an authorization key in the headers: (will be done in every request). This unreadable string is decoded with base64:

So we can catch this at server-side:

[HttpGet]
public IActionResult Login([FromHeader] string authorization)
        {
            Response.Headers.Add("WWW-Authenticate", "basic");

            var result = "";
            if (authorization != null)
            {
                result = base64Decode(authorization.Substring(6));

if(GetUsername(result).Equals("tom") && GetPassword(result).Equals("tom"))
                {
                    return Ok();
                }
            }
            return Unauthorized();
        }

public string base64Decode(string data)
        {
            try
            {
                System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
                System.Text.Decoder utf8Decode = encoder.GetDecoder();

                byte[] todecode_byte = Convert.FromBase64String(data);
                int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
                char[] decoded_char = new char[charCount];
                utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
                string result = new String(decoded_char);
                return result;
            }
            catch (Exception e)
            {
                throw new Exception("Error in base64Decode" + e.Message);
            }
        }
        public string GetUsername(string data)
        {
            var result = data.Substring(0, data.IndexOf(":"));

            return result;
        }
        public string GetPassword(string data)
        {
            var result =  data.Substring( data.IndexOf(":")+1, data.Length-1- data.IndexOf(":"));            
            return result;
        }


I implemented a base64 decoder, and based on the string “username:password” I created simple helper methods the get the username and password. If the username and password are right, I send an 200 OK status code.

More info on status codes:

https://restfulapi.net/http-status-codes/

You also can logout, by writing a logout action method:

  [HttpGet("Logout")]
        public IActionResult LogOut()
        {
            return Unauthorized();
        }

Off course when you implement a basic authentication mechanism , for every request you always need to check your authentication credentials. To automate this process we can implement middleware.

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1

Middleware is code that’s collected into a pipeline to handle requests and responses. You can write your own middleware, because it’s a class with some requirements:

  • in the constructor of your middleware class you need to pass a request delegate so you can pass the request to the next component in the pipeline


https://medium.com/@hidayatarg/asp-net-core-jwt-authentication-middleware-reading-a-jwt-14cdb32e39ca

Let’s write my own middleware class so I don’t have to take care of the authentication mechanism in my controllers:

class AuthMiddleWare
    {
        private RequestDelegate next;

        public AuthMiddleWare(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            var auth = context.Request.Headers.FirstOrDefault(header => header.Key.ToLower() == "authorization");
            if (string.IsNullOrEmpty(auth.Value))
            {
                context.Response.Headers.Add("WWW-Authenticate", "Basic");
                context.Response.StatusCode = 401; //401 error in response, so next middleware pipeline item not invoked
            }
            else
            {
                //TODO check username & password and correct, invoke next middleware
                await next.Invoke(context);

            }
        }
    }

You can see the constructor with the RequestDelegate as an argument, and in the Invoke method, I will execute my code. I cannot send an Ok() message or Unauthorized() message as in my action methods, but I can set some Key Values in my header by the HttpContext argument.

The HttpContext encapsulates all the HTTP-information about an individual HTTP request:

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpcontext?view=aspnetcore-3.1

Summary:

  • Basic authentication is not secured, because it’s decoded as a base64 string.
  • The browser stores the base64 string in the cache so it will not request the user needs to fill in his credentials at every request
  • Credentials are added in the headers at every request
  • If you use basic authentication, you better set up HTTPS (SSL)

Cookie Authentication

What is a cookie? A cookie is a header field created by the server into the response. The browser will always read the cookie value and stores it per domain and sends the cookie with every request to the server. (you don’t need to write any javascript code for this).

A cookie has a limited amount of data: 4096 bytes, and has an expiration DateTime. (you need to set up at server-side).

How it works:


https://www.oreilly.com/library/view/angular-6-by/9781788835176/9ba8a795-924e-4cca-80a5-87ef93cf1211.xhtml

1. Clients send a request but get an Unauthorized response.

2. Clients send username and password

3. The server verifies credentials and sets auth cookie

4. The client always sends a cookie 5. The server needs to validate the cookie Set cookie at server-side I create a new controller and a new Action method:

[Route("api/[controller]")]
    [ApiController]
    public class CookieController : ControllerBase
    {

        [HttpGet]
        public IActionResult CreateCookie()
        {
            //in the response I get a list of cookies
            Response.Cookies.Append("CookieTom", "test", new CookieOptions() { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(1)) });
            return Ok();
        }
    }
}

In the response, I can add a new cookie and give it a key-value row with extra parameters, like an expire field. ( as a test it set 1 minute). Start your API application and check the header fields in the development tools of the browser:

You can switch to the Application tab where you can see all cookies per domain:

If you do a request to another action method (another controller), you can see the cookie is added into the request headers:

After the expire-time, you need to request a new cookie.

In the middleware, you can write code to read your cookie and decide the user can have access to your requested action method.

Claim-based authentication

The trend nowadays is to delegate authentication to 3rd party identity providers. The providers are using a claim-based mechanism, meaning the user logs in with an identity provider like Google or Facebook. The provider checks the credentials but will not release the identity, but will provide you with claims which can be used to authorize the user in your application. Claims or ‘things’ about the logged-in user, e.g. first name, last name, date of birth, email, … What kind of claims you get depends on the identity provider.

This claim-based mechanism is baked into ASP.NET Core, and we can use this with middleware to login with cookie-based authentication.

In practice:

Create a new ClaimsController, and add a new Login ActionMethod

namespace Auth_Demo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ClaimsController : ControllerBase
    {

        [HttpPost]
        public IActionResult Login(string username, string password)
        {
            //Check your username & password
            //now I will use claims of the ASP.NET CORE frameword
            var claims = new List();
            claims.Add(new Claim("username", username));

            //with the claims we can create an Identity:
            var identity = new ClaimsIdentity(claims, "claims");
            //with the identity I need to crete a principal, and with the principal i can login in ASP.NET CORE
            var principal = new ClaimsPrincipal(identity);

            //login:
            HttpContext.SignInAsync(principal);
            return Ok();


        }
    }
}

In the action method, I check the username and password, and if this is ok, I will create a claim. You can do this with the build-in functionality of the ASP.NET CORE framework. Next, you need to create an identity based on the claims, and a new ClaimsPrincipal based on your identity. Finally you log in with the HttpContext.SignInAsync.

With signInAsync there will be an encrypted cookie created and added to the current response.

namespace Auth_Demo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ClaimsController : ControllerBase
    {

        [HttpPost]
        public IActionResult Login(string username, string password)
        {

            if (username.Equals("tom") && password.Equals("tom"))
            {
                //Check your username & password
                //now I will use claims of the ASP.NET CORE frameword
                var claims = new List();
                claims.Add(new Claim("username", username));

                //with the claims we can create an Identity:
                var identity = new ClaimsIdentity(claims, "claims");
                //with the identity I need to crete a principal, and with the principal i can login in ASP.NET CORE
                var principal = new ClaimsPrincipal(identity);

                //login:
                HttpContext.SignInAsync(principal);
                return Ok();
            }
            else
                return Unauthorized();


        }
    }
}

Extra reading:

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1 https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimsidentity?view=netframework-4.8 https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimsprincipal?view=netframework-4.8

Next, I will make use of existing middleware, but you need to activate the middleware in the startup class. So you have to say you will use cookie authentication, and in the pipeline (the configure method), you have to set up you will use authentication and authorization.

  public void
ConfigureServices(IServiceCollection services)
{

services.AddAuthentication("cookieauthentication").AddCookie("cookieauthentication", settings =>
{
settings.Cookie.Name = "AuthCookie";
});
services.AddControllers();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

// app.UseMiddleware<AuthMiddleWare>();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}



app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthentication();

app.UseAuthorization();

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

If you start the application and go to …/api/claims?username=tom&password=tom you will see that middleware created a cookie with the claims information:

The browser will send the cookie with every request. Via the HttpContext object (it’s a controller object) you have access to the claims of the user. You can test this by going to your claims controller first and log in with the right username and password, next you can go to another action method of a controller and check the HttpContext.User object. E.g. I created a projectscontroller with a GetProject() Action method:

  [Route("api/[controller]")]
    [ApiController]
    public class ProjectsController : ControllerBase
    {
        [HttpGet]
        public IActionResult GetProject()
        {
            var debug = HttpContext.User;

            return Ok("project1");
        }
    }

In my debug I check the claims list.

Now you can see that you can check your access in every action method by reading the claims list and check for the right claim, but you also can automate the process by adding an attribute per action method of for all action methods by adding the attribute at the controller:

     [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ProjectsController : ControllerBase
    {
        [HttpGet]
        public IActionResult GetProject()
        {
            var debug = HttpContext.User;

            return Ok("project1");
        }
    }

If you try to reach the GetProject() Action method, you will automatically be redirected to: https://localhost:44344/Account/Login?ReturnUrl=%2Fapi%2Fprojects

Before you reach this action method, the framework checks if the user is authenticated before you get access. If not authorized you get a 302 message, which is a redirect message. So if the user is not logged in, the framework says that you need to be redirected to the login page (/account/login ).

If you go to the claims controller first https://localhost:44344/api/claims?username=tom&password=tom And next, you will go back to your projects controller: https://localhost:44344/api/projects

You will see it works now!

You can customize the redirectlogin by adding extra parameters in the middleware. You need to customize this in the startup, so you return e.g. a 401 message (unauthorized); which is better for an API:

public void ConfigureServices(IServiceCollection services)
        {

            services.AddAuthentication("cookieauthentication").AddCookie("cookieauthentication", settings =>
            {
                settings.Cookie.Name = "AuthCookie";
                settings.Events.OnRedirectToLogin = (context) =>
                {
                    context.Response.StatusCode = 401; //unauthorized
                    return Task.CompletedTask;
                };
            });
            services.AddControllers();
        }


To remove the cookie and logout I can implement a LogOut method in my claimscontroller:

  public IActionResult Logout()
        {
            //cookie will be deleted
            HttpContext.SignOutAsync();
            return Ok();
        }

3rd party identity provider

You don’t need to keep credentials at your own server, but you will use 3rd party credentials like Microsoft, Google, Facebook. The protocol you use is OAuth of OpenId. This protocol gives you the possibility to work with a 3rd party Identity Framework. You as a developer can choose which provider you work with. OAuth protocol With the OAuth protocol, you delegate the authentication functionality to an authorization server. The disadvantage is the complexity of the login flow (see scheme).

1. Client request access to the client application

2. The authorization server will send login form because you will be redirected by our own server

3. Between server and client applications, there will be a messaging with tokens and code.

https://docs.oracle.com/cd/E50612_01/doc.11122/oauth_guide/content/oauth_intro.html

Token-based authentication

See next topic and:

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-3.1
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/social-without-identity?view=aspnetcore-3.1