Adding ASP.NET Security to Angular

November 26, 2017

Overview

The purpose of this blog post is to provide the instructions for adding ASP.NET security to the default Angular template application available in Visual Studio .NET 2017. It’s non-trivial and, I believe Microsoft would provide a much better experience for Enterprise developers if they supplied this as an option out-of-the-box.

The way I do this is to create two applications, one is the default angular application, and the second is a default mvc application with individual security, then I just copy all the important bits from the mvc app across to the angular app.

The instructions

1. Prerequisites

a) Visual Studio Professional 2017 (Version 15.3.3 or newer)\

b) Dot net core 2.0 framework, available from https://www.microsoft.com/net/download/core

2. File -> New Project, Select ASP.NET Core Web Application. Name it (I called mine AngularWithSecurity) and put it in the location that you want. Click OK.

3. In the New ASP.NET Core Web Application screen, select .NET Framework from the first dropdown at the top, and select ASP.NET Core 2.0 in the second drop down. (If ASP.NET Core 2.0 is missing, there should be a yellow bar at the top, which you can click on to install it.)

4. Select Angular as the template. It doesn’t provide security out of the box, so just click Ok to continue.

5. It creates the initial structure and runs npm install. npm is the node package manager. Whereas the nuget package manager will work seamlessly through a proxy, npm will not, and it’s configuration can be a little bit tricky especially through corporate firewalls. If npm can’t get through the firewall, you’ll need to got to path c:\Users\[your usename] and add a file called .npmrc that contains something like:

proxy=http://proxy.xxx:8080
strict-ssl=false
https-proxy=http://proxy.xxx:8080
registry=http://registry.npmjs.org

If npm is not installed on your system, go the web site nodejs.org and install the node 64 bit v6.11.3 LTS version. Npm (node package manager) comes with it.

6. Build the application and make sure it runs. Note that it could take some time in your environment to load all the packages the first time, perhaps even 5 to 10 minutes. When there’s no proxy involved, it can be almost instantaneous.

It should show a generic Angular app that provides a counter and shows how to fetch data via a web api call, but no database interaction. Now stop the application.

7. Create another application, File -> New Project and call it DefaultMVCApplication. We will generate an MVC application with security so that we can easily copy the bits we need to the angular application. Click OK to create the application.

8. Select .NET Framework and ASP.NET Core 2.0 in the dropdowns and then choose the Web Application (Model-View-Controller) template. Click the Change Authentication button and choose Individual User Accounts. Make sure “Store user accounts in-app” is selected. Then click OK to close that dialog box. Then click OK to create the MVC application.

9. Build that application to make sure everything is installed properly.

10. In the AngularWithSecurity application, right click on the project and click on Manage Nuget Packages. Add the following packages to the application:

EntityFramework (Latest stable 6.1.3)

AspNetCore.Authentication.Cookies (2.0)

AspNetCore.Identity.EntityFrameworkCore (2.0)

EntityFrameworkCore.SqlServer (2.0)

EntityFrameworkCore.Tools (2.0)

11. Add a new folder at the top level (the same level as ClientApp) called Models. Copy the entire Models folder from the DefaultMVCApplication into the Models folder of the AngularWithSecurity application.

12. Do the same with the Data folder:

Add a new folder at the top level (the same level as ClientApp) called Data. Copy the entire Data folder from the DefaultMVCApplication into the Data folder of the AngularWithSecurity application.

13. Do a global replace to replace the word DefaultMVCApplication with the word AngularWithSecurity.(EditàFind and ReplaceàReplace in all files, Find “DefaultMVCApplication” Replace with “AngularWithSecurity”, Entire Solution, then click Replace All. Click Yes, you are ok with changing it blindly. ) Check one of the files just imported to make sure the change happened.

14. In our environment, we have a slight change to the LoginViewModel to support usernames instead of email addresses for logging in. Go to the Models/AccountViewModels/LoginViewModel.cs file and add a UserName property. Make sure you add a Required attribute to the UserName and remove the Required attribute from the EmailAddress. Mine looks like this:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace AngularWithSecurity.Models.AccountViewModels
{
  public class LoginViewModel
  {
    [Required]
    public string UserName { get; set; }

    [EmailAddress]
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }
  }
}

15. Do the same with the RegisterViewModel class. Mine looks like this:

public class RegisterViewModel
{
  [Required]
  public string UserName { get; set; }

  [EmailAddress]
  [Display(Name = "Email")]
  public string Email { get; set; }
[Required]
  [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
  [DataType(DataType.Password)]
  [Display(Name = "Password")]
  public string Password { get; set; }
[DataType(DataType.Password)]
  [Display(Name = "Confirm password")]
  [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
  public string ConfirmPassword { get; set; }
}

16. Go to the Controllers folder and add a controller called AccountController. My AccountController contains the following code. You should be able to add just the properties and methods into the main class. This is a stripped down version of the AccountController class found in the DefaultMVCApplication project.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using YourNamespace.Models;
using Microsoft.AspNetCore.Authentication;
using AngularWithSecurity.Models.AccountViewModels;

// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace AngularWithSecurity.Controllers
{
  [Authorize]
  public class AccountController : Controller
  {
    private readonly UserManager _userManager;
    private readonly SignInManager _signInManager;

    public AccountController(
      UserManager userManager,
      SignInManager signInManager)
    {
      _userManager = userManager;
      _signInManager = signInManager;
    }

    //
    // GET: /Account/Login
    [HttpGet]
    [AllowAnonymous]
    public async Task Login(string returnUrl = null)
    {
      // Clear the existing external cookie to ensure a clean login process
      await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
      ViewData["ReturnUrl"] = returnUrl;
      return View();
    }

    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task Login(LoginViewModel model, string returnUrl = null)
    {
      ViewData["ReturnUrl"] = returnUrl;
      if (ModelState.IsValid)
      {
        var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
          return RedirectToLocal(returnUrl);
        }
        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        return View(model);
     }

     // If we got this far, something failed, redisplay form
     return View(model);
   }

   //
   // GET: /Account/Register
   [HttpGet]
   [AllowAnonymous]
   public IActionResult Register(string returnUrl = null)
   {
     ViewData["ReturnUrl"] = returnUrl;
     return View();
   }

   //
   // POST: /Account/Register
   [HttpPost]
   [AllowAnonymous]
   [ValidateAntiForgeryToken]
   public async Task Register(RegisterViewModel model, string returnUrl = null)
   {
     ViewData["ReturnUrl"] = returnUrl;
     if (ModelState.IsValid)
     {
       var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };
       var result = await _userManager.CreateAsync(user, model.Password);
       if (result.Succeeded)
       {
         // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
         // Send an email with this link
         // var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
         // var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
         // await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
         //    $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");

         await _signInManager.SignInAsync(user, isPersistent: false);
         return RedirectToLocal(returnUrl);
       }
       AddErrors(result);
    }
    // If we got this far, something failed, redisplay form
    return View(model);
  }  

  //
  // POST: /Account/Logout
  [HttpPost]
  [ValidateAntiForgeryToken]
  public async Task Logout()
  {
    await _signInManager.SignOutAsync();
    return RedirectToAction(nameof(HomeController.Index), "Home");
  }

  #region Helpers
  private void AddErrors(IdentityResult result)
  {
    foreach (var error in result.Errors)
    {
      ModelState.AddModelError(string.Empty, error.Description);
    }
  }

  private IActionResult RedirectToLocal(string returnUrl)
  {
    if (Url.IsLocalUrl(returnUrl))
    {
      return Redirect(returnUrl);
    }
    else
    {
      return RedirectToAction(nameof(HomeController.Index), "Home");
    }
  }

  #endregion

  }

}

17. If any of the fields in the AccountController class have red squiggly lines under them, it means that they need to have references added. Go over each one, click on Ctrl-. (Ctrl dot) and then select the first item on the tooltip helper for each one (I usually just click Enter when the tooltip appears).

18. At this stage, everything is compiling on my machine. Build the application to make sure everything compiles so far for you. If not, you need to go back and work through the steps to see what you’ve missed.

19. Go to the HomeController class in Controllers folder and add an Authorize attribute to the controller. This will make it require authorisation when attempting to access the main page. Mine looks like this:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace AngularWithSecurity.Controllers
{
  public class HomeController : Controller
  {

    [Authorize]
    public IActionResult Index()
    {
      return View();
    }

    public IActionResult Error()
    {
      ViewData["RequestId"] = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
      return View();
    }
  }
}

20. Add a default database connection string into the appsettings.json file found in the root folder. Make sure you update the user id and password with valid values for you. This will give you a different sql profiler id which will help with debugging. The ASP.NET security database is found in the pool_master database. Mine looks like this:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=111.222.333.444\\YourSqlInstance,54321;Database=yourdatabasename;User Id=yourusername;Password=xxxxx;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  }
}

21. Create a new folder at the root level (same level as ClientApp) and call it Filters. Add a class called AngularAntiForgeryCookieResultFilter. Mine looks like this:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AngularWithSecurity.Filters
{
  public class AngularAntiforgeryCookieResultFilter : ResultFilterAttribute
  {
    private IAntiforgery antiforgery;
    public AngularAntiforgeryCookieResultFilter(IAntiforgery antiforgery)
    {
       this.antiforgery = antiforgery;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
      if (context.Result is ViewResult)
      {
        var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
        context.HttpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
      }
    }
  }
}

22. Go to the Startup.cs file and make modifications to the ConfigureServices method, as follows. Make sure you get rid of any red squiggly lines by using the Ctrl-. trick.

public void ConfigureServices(IServiceCollection services)
{
  services.AddDbContext(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  services.AddIdentity()
          .AddEntityFrameworkStores()
          .AddDefaultTokenProviders();

  //services.AddOptions();
  // services.Configure(Configuration);
  // Add framework services.
  services.AddAntiforgery(opts => opts.HeaderName = "X-XSRF-Token");
  services.AddMvc(opts =>
  {
    opts.Filters.AddService(typeof(AngularAntiforgeryCookieResultFilter));
  })
  .AddJsonOptions(options=> {
    options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
  });
  services.AddTransient();
}

(You might need to adjust the time zone handling above to suit your own circumstances)

23. Still an Startup.cs, in another method, this time called Configure, add a line to tell ASP.NET Core to use the Authentication framework.

24. Find the Views folder at the root level and open the _ViewImports.cshtml file. Make sure it has code like this, replacing the Application name with your own:

@using AngularWithSecurity
@using AngularWithSecurity.Models
@using AngularWithSecurity.Models.AccountViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.SpaServices

25. Still in the Views folder at the root level, add a new folder called Account. Inside this folder, add a new item, select the “MVC View Page” template, and call it Login.cshtml, then click Add. Then cut and paste the following code (remembering to replace the application name.)


@using System.Collections.Generic
@using System.Linq
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Http.Authentication
@model AngularWithSecurity.Models.AccountViewModels.LoginViewModel

.banner {
background: #32408f;
height: 64px;
width: 100%;
padding-left: 15px;
padding-top: 20px;
}

.banner h1 {
height: 30px;
display: table-cell;
vertical-align: middle;
color: #eee;
margin: 0;
float: left;
font-size: 24px;
}

.banner img {
margin-top: -6px;
float: left;
height: 36px;
}

.search-overlay {
background: rgba(68,138,255, 0.50);
border: 1px solid rgba(63,81,181, 0.9);
margin-top: 60px;
/*height: 233px;*/
height: 240px;
font-weight: bold;
width: 400px;
padding: 40px;
font-family: "Roboto";
margin-left: auto;
margin-right: auto;
display: block;
}

.search-overlay .row {
margin-top: 3px;
}

.search-overlay label {
margin-top: 2px !important;
font-size: 18px;
font-weight: normal;
color: #fff;
}

.search-overlay input {
color: #fff;
font-size: 18px;
font-weight: normal;
}

.search-overlay button {
color: #fff !important;
background-color: #448aff;
border: 1px solid #3077eb;
}

.main-login-div {
background-image: url(/images/main-banner.jpg);
background-color: #d9dfe3;
background-repeat: no-repeat;
background-attachment: fixed;
background-position: top left;
height: 100%;
min-height: 1000px;
min-width: 600px;
padding: 0;
margin: 0;
}

.nopadding {
padding: 0;
}

.validation-summary-errors {
background: #fff;
width: 318px;
color: red;
margin-top: 40px;
text-align: center;
}

.validation-summary-errors ul {
list-style: none;
padding: 10px;
}

<div class="container-fluid nopadding" style="overflow-x:hidden;">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-xs-12">
<div class="banner">
<div class="pull-left">
<img src="image/png;base64,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">
<h1>Site title</h1>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12" style="height:44px;background-color:#448aff;"></div>
</div>
</div>
<div class="row main-login-div">
<div class="col-xs-12">

<div class="search-overlay">
<div>
<div class="row">
<div class="col-xs-12">
Login:

<span class="text-danger"></span></div>
</div>
<div class="row">
<div class="col-xs-12">
Password:

<span class="text-danger"></span></div>
</div>
<div class="row">
<div class="col-xs-offset-4 col-xs-8" style="margin-top:8px;">

</div>
</div>
<div class="row">
<div class="col-xs-4"><a href="/Account/Register" style="color:#fff;text-decoration:underline;font-weight:normal;">Register</a></div>
<div class="col-xs-8">Login <i class="fa fa-key"></i></div>
</div>
<div class="row">
<div class="col-xs-12">
<div></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
}

26. Add another .cshtml file, this time called Register.cshtml. Mine looks like this:


@model RegisterViewModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"].</h2>

<h4>Create a new account.</h4>

<hr />

<div class="text-danger"></div>
<div class="form-group">

<div class="col-md-10">

<span class="text-danger"></span></div>
</div>
<div class="form-group">

<div class="col-md-10">

<span class="text-danger"></span></div>
</div>
<div class="form-group">

<div class="col-md-10">

<span class="text-danger"></span></div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
Register</div>
</div>


@section Scripts {
}

27. Next, I will add a bit of angular code so that we can see who the logged in user is. Go into the SampleDataController class, found in the Controllers folder. Add the following method, which returns the currently logged in user name:


[HttpGet("[action]")]
public string WhoAmI()
{
  return this.User.Identity.Name;
}

28. Go to the fetchdata.component.ts file, found in the ClientApp/app/components/fetchdata folder. Add the following code, which I have highlighted, to the constructor method of the FetchDataComponent:


import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';

@Component({
selector: 'fetchdata',
templateUrl: './fetchdata.component.html'
})
export class FetchDataComponent {
public forecasts: WeatherForecast[];
public whoami: string = 'no-one';

constructor(http: Http, @Inject('BASE_URL') baseUrl: string) {
http.get(baseUrl + 'api/SampleData/WeatherForecasts').subscribe(result => {
this.forecasts = result.json() as WeatherForecast[];
}, error => console.error(error));

http.get(baseUrl + 'api/SampleData/WhoAmI').subscribe(result => {
this.whoami = result.text();
}, error => console.error(error));
}
}

interface WeatherForecast {
dateFormatted: string;
temperatureC: number;
temperatureF: number;
summary: string;
}

29. Go to the fetchdata.component.html file. Add the following highlighted code to the fetchdata.component.html page:

<h1>Weather forecast</h1>
This component demonstrates fetching data from the server.
<p><em>Loading...</em></p>

Congratulations, you are: {{whoami}}
<table class='table'>
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ forecast.dateFormatted }}</td>
<td>{{ forecast.temperatureC }}</td>
<td>{{ forecast.temperatureF }}</td>
<td>{{ forecast.summary }}</td>
</tr>
</tbody>
</table>

30. Right click on the project file and click on Properties. Go to the Debug tab and scroll down to Web Server Settings. Tick the Enable SSL box, then click on Copy to copy the SSL url to the clipboard. Select the App URL and right-click, paste, to paste the https url into the debug box. The application should now compile.

31. Run the application. It should automatically redirect you to the login page. Log in using a valid name and password. The home page should appear. Click on Fetch data. It should fetch data from the Web Api, and also return who the logged in user is. My page looks like this:

weather-forecast

And that’s it, you’ve added ASP.NET security to the default Angular application in Visual Studio!