Advanced Ajapa Yoga Kriyas and Meditations for Software Developers : Tap the power of breath, mantra, mudra, and dhyana for improved focus, peace of mind, and blissful inner connection.


Use Cookie Authentication with Web API and HttpClient

Recently I wrote this article explaining the cookie authentication in ASP.NET Core. A reader asked whether cookie authentication can be used with ASP.NET Core Web API and that too when the Web API is being consumed using HttpClient component. This article explains a possible solution to the problem.

A word of caution

Cookie authentication works great with web applications because everything runs within a browser. Various pieces of this security scheme such as authentication cookie and automatic redirection to the login page work great in the  browser. Although you can use cookie authentication with Web API (because Web API controller is also a controller), doing so is not always recommended.

Web API is a service and doesn't have any UI elements. So, features such as redirection URL don't apply to a Web API. Moreover, a Web API can be consumed by variety of clients including Single Page Applications (SPAs) and non-browser clients. For example, a windows application may utilize a Web API to get some job done. In such cases the client application may not be able to deal with the cookie issued as a part of the authentication scheme. Therefore, you should carefully think whether you want to use cookie authentication with Web API. There are better alternatives for Web API security such as Json Web Tokens (JWT) that you can use instead of cookie authentication.

However, if for some reason you want to implement cookie authentication for Web API you can use the technique illustrated in the remainder of this article. I am going to use the same ASP.NET Core application that you developed in this article.

Creating a sample Web API

As an example let's create a Web API that has the following actions :

  • Login() : This action will do the task of validating a user's credentials and will issue the authentication cookie accordingly.
  • Logout() : This action will remove the authentication cookie thus logging the use out of the system.
  • Get() : This action is actual Web API action that handles GET verb and returns data to the caller.

The Login() and Logout() actions will not be auto-mapped to any specific HTTP verb. That's because your Web API might be need auto-mapping for its main actions (say, Post() action). The routing of these two actions is configured differently so that the client can explicitly call them to perform the respective tasks.

The Get() action is a typical Web API action and maps to GET verb. Although our sample Web API doesn't include POST, PUT, and DELETE actions you can add them as per your need. In this example our primary focus will be Login() and Logout().

Add a new Web API controller - ValuesController - to the project. The Web API constructor receives the MyAppDbContext object and is shown below :

public class ValuesController : Controller
{
    private MyAppDbContext db;

    public ValuesController(MyAppDbContext db)
    {
        this.db = db;
    }
}

Notice that the ValuesController doesn't have [Route] attribute added to it. That's because we configure routing at individual action level as you will see later.

The Login() action

The Login() action of the ValuesController is shown below :

[Route("api/[controller]/Login")]
[HttpPost]
public IActionResult Login([FromBody]LoginViewModel model)
{

    bool isUservalid = false;

    MyAppUser user = db.MyAppUsers.Where(usr => 
usr.UserName == model.UserName && 
usr.Password == model.Password).SingleOrDefault();


    if (user != null)
    {
        isUservalid = true;
    }


    if (isUservalid)
    {
        var claims = new List<Claim>();

        claims.Add(new Claim(ClaimTypes.Name, user.UserName));

        string[] roles = user.Roles.Split(",");

        foreach (string role in roles)
        {
            claims.Add(new Claim(ClaimTypes.Role, role));
        }

        var identity = new ClaimsIdentity(
            claims, CookieAuthenticationDefaults.
AuthenticationScheme);

        var principal = new ClaimsPrincipal(identity);

        var props = new AuthenticationProperties();
        props.IsPersistent = model.RememberMe;

        HttpContext.SignInAsync(
            CookieAuthenticationDefaults.
AuthenticationScheme,
            principal,
            props).Wait();

        return new ObjectResult("Success");
    }
    else
    {
        return new ObjectResult("Error");
    }
}

The Login() action is decorated with two attributes - [Route] and [HttpPost]. The [Route] attribute configures the routing such that the Login() method has an end point URL : api/values/login

The [HttpPost] attribute indicates that Login() should be invoked with a POST request.

The Login() accepts an object of LoginViewModel. We created this class earlier while coding the AccountController. The LoginViewModel contains three properties - UserName, Password, and RememberMe. The client application needs to sent this object while attempting to login.

The code then checks whether user credentials are valid. This is done by fetching a MyAppUser object matching the specified UserName and Password. If those details are found, the code creates a list of Claim objects, ClaimsIdentity, ClaimsPrincipal, and AuthenticationScheme. This code should be quite familiar to you from the previous article and hence I am not going to discuss it again here.

What's important here is to notice that Login() is issuing the authentication cookie by calling SignInAsync(). Moreover, it returns a success string "Success" to the client. If the user can't be validated we don't issue the authentication cookie and we return "Error" to the caller. This way the client can easily determine whether the login attempt was successful or not. Of course, you can also use some HTTP status code based mechanism.

The Logout() action

The Logout() acrtion of ValuesController is shown below :

[Route("api/[controller]/Logout")]
[HttpPost]
public IActionResult Logout()
{
    HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
    return new ObjectResult("Success");
}

The [Route] attributed added to the Logout() action configures its end point URL to /api/values/logout. The [HttpPost] attribute indicates that Logout() will be invoked using a POST request.

Inside, we simply call SignOutAsync() to remove the authentication cookie issued in the Login() action. 

The Get() action

The Get() action simply returns a string value to the caller and is shown below :

[Authorize]
[Route("api/[controller]")]
[HttpGet]
public IActionResult Get()
{
    string userName = HttpContext.User.Identity.Name;

    if (HttpContext.User.IsInRole("Administrator"))
    {
        return new ObjectResult($"Welcome 
{userName} (Administrator)!");
    }
    else
    {
        return new ObjectResult($"Welcome 
{userName} (Normal User)!");
    }

}

Notice the code marked in bold latters. The Get() action is a secured action and hence we decorate it with [Authorize] attribute. The [Route] and [HttpGet] attributes map the Get() action to the GET requests.

Inside, we grab the current user's name using the HttpContext.User.Identity.Name property. We also check whether the user belongs to Administrator role using HttpContext.User.IsInRole() method. Accordingly we return a string with a welcome message for that user.

This completes the Web API. Now let's go ahead and consume it using HttpClient component.

Calling Web API using HttpClient

Now comes the important part - consuming the Web API from a client application using HttpClient component.

You will need to add two actions to the HomeController - CallApi() and Logout(). You will also need to add a public constructor as shown below :

private HttpClient client;

public HomeController()
{
    HttpClientHandler clientHandler = 
new HttpClientHandler();
    clientHandler.UseCookies = true;
    clientHandler.CookieContainer = new CookieContainer();

    client = new HttpClient(clientHandler);
    client.BaseAddress = new Uri("http://localhost:49273");
    MediaTypeWithQualityHeaderValue contentType = 
new MediaTypeWithQualityHeaderValue("application/json");
    client.DefaultRequestHeaders.Accept.Add(contentType);
}

The above code creates an instance of HttpClient component and configures it for calling the Web API.

Notice the code marked in bold letters. It creates a HttpClientHandler object and sets its UseCookies property is set to true.  The value of true indicates that the HttpClientHandler should use the CookieContainer to store and send server cookies. The CookieContainer property holds a reference to a new CookieContainer object.

The HttpClientHandler object is then sent to the HttpClient's constructor. Properties of HttpClient such as BaseAddress and accept header are configured as you normally do.

Now that we have configured the HttpClient let's see how the CallApi() action calls the Web API actions. Have a look at the following code :

public IActionResult CallApi()
{
    string loginData = JsonConvert.SerializeObject(
new { UserName = "TestUser", 
Password = "TestPassword", 
RememberMe = false });

    StringContent content = new StringContent(
loginData,System.Text.Encoding.UTF8,"application/json");

    HttpResponseMessage loginResponse = 
client.PostAsync("/api/values/login", content).Result;

    string loginResult = loginResponse.Content.
ReadAsStringAsync().Result;

    loginResult = JsonConvert.
DeserializeObject<string>(loginResult);

    if (loginResult=="Success")
    {
        HttpResponseMessage getResponse = 
client.GetAsync("/api/values").Result;

        ViewData["message"] = JsonConvert.
DeserializeObject<string>
(getResponse.Content.ReadAsStringAsync().Result);
    }
    else
    {
        ViewData["message"] = "Error while calling Web API!";
    }

    return View();
}

Note that our Web API contains Get() action that is secured using the [Authorize] attribute. So, before you invoke Get() you must log in to the system.

Therefore, the code first calls the Login() action of the Web API using a PostAsync() method. The login details such as UserName, Password, and RememberMe are also sent along with the call as a JSON string.

Recollect that the Login() action of the Web API returns "Success" if the user credentials are valid. If the login attempt is successful the code goes ahead and calls the Get() action using the GetAsync() method. The return value of Get() is stored inside ViewData for the sake of displaying on a view.

What happens if you try to call Get() directly without first calling Login()? In that case the Get() won't be called and the application will try to redirect t o the login page. This redirection is a tricky thing in this approach and I have already highlighted it at the beginning of this article.

A sample successful run of the CallApi() action is shown below :

The markup that goes behind the above view is this :

<h1>@ViewData["message"]</h1>
<form asp-controller="Home" 
asp-action="Logout" method="post">
    <input type="submit" value="Logout" />
</form>

The Logout button submits the form to the Logout() action of HomeController which in turn calls the Logout() action of the Web API :

[HttpPost]
public IActionResult Logout()
{
    HttpResponseMessage logoutResponse = 
client.PostAsync("/api/values/logout", null).Result;

    string logoutResult = logoutResponse.Content.
ReadAsStringAsync().Result;

    logoutResult = JsonConvert.
DeserializeObject<string>(logoutResult);

    ViewData["message"] = logoutResult;

    return View();
}

The Logout view simply displays the value of message from ViewData and hence is not discussed here.

Ok. Set a breakpoint at the CallApi() action. Run the application, enter /home/callapi in the addressbar and see how the code works.

That's it for now! Keep coding !!


Bipin Joshi is an independent software consultant and trainer by profession specializing in Microsoft web development technologies. Having embraced the Yoga way of life he is also a meditation teacher and spiritual guide to his students. He is a prolific author and writes regularly about software development and yoga on his websites. He is programming, meditating, writing, and teaching for over 27 years. To know more about his ASP.NET online courses go here. More details about his Ajapa Japa and Shambhavi Mudra online course are available here.

Posted On : 26 February 2018







Advanced Ajapa Yoga Kriyas and Meditations for Software Developers : Tap the power of breath, mantra, mudra, and dhyana for improved focus, peace of mind, and blissful inner connection.