Creating a security-enabled AngularJS application with ASP.NET MVC the easy way (tutorial)
In recent years Single Page Applications (SPAs) have become popular and there are plenty JavaScript frameworks which make it relatively easy to develop them. Angular, which is developed Google, seems to be the one with the most momentum.
In this post I’ll explain how you can use AngularJS together with ASP.NET to create Angular apps together with ASP.NET Web APIs. For this post I used the Community Editions of Visual Studio 2015 with MVC 4 and .NET Framework 4.6. I used the latest production version of Angular 1. I also used TypeScript and Bootstrap. There are many great tutorials on the web for each of these technologies. Together Bootstrap, Angular and TypeScript (the ‘BAT’ stack) complement each other to create a great client-side stack for building modern web applications.
The role of ASP.NET is to host our server-side logic. There are two important parts to this: Web APIs are used to expose REST endpoints which are backed by .NET code. Additionally we’ll leverage ASP.NET’s rich identity framework to implement security. ASP.NET’s identity framework supports many powerful features including social login, two-factor authentication and active directory support.
This tutorial assumes you’re already familiar with the basics of ASP.NET, AngularJS and Bootstrap. If you want to learn more about any of these topics. there are lots of other good tutorials on the net.
At the end of this tutorial you’ll have a skeleton Angular application backed with a simple Web API and security enabled so that users of the app will be presented a login experience. You can find the source code for this project on GitHub: https://github.com/andreasderuiter/bat-mvc.
Something to understand before we get started
In this tutorial we will not implement a login page within the Angular app itself. Instead we’ll create the boilerplate for an AngularJS app which will integrate with the existing login pages which are part of the standard MVC project template.
At first, this approach may seem non-optimal because the whole point of a SPA application is to embed all functionality in a single page. In my perspective however, the goal is to create a fluid and responsive user experience and SPA is just a means to an end. The fact that a user will temporarily be redirected to a login page, which will add 1 second of delay once per user session, is a small price to pay for the benefits we get from using this approach:
- The solution is very simple and easy to understand
- No extra code means no bugs and minimal cost of maintenance
- The solution fully supports all of the features of MVCs authentication system
- If you have a traditional non-SPA web application which you want to migrate to Angular, this offers a smoother migration path than rewriting all of this in Angular at once.
Nonetheless, if you wish to implement authentication within your Angular app, here are some resources:
- https://www.codeproject.com/Articles/806029/Getting-started-with-AngularJS-and-ASP-NET-MVC-Par. This tutorial shows how you can implement authentication in an easy to understand way.
- https://bitbucket.org/david.antaramian/so-21662778-spa-authentication-example/overview. Another tutorial.
- https://github.com/JustMaier/MVC5-SPA-Angular. This is a component which you can include as a script library in your app. This library works with MVC5 (not MVC4 which we’re using in this tutorial)
If you’re not sure which way to go, my advice would be to follow this tutorial first so you quickly have a working solution and can then focus on the actual core of your app. There’s nothing that will prohibit you from adding authentication plumbing directly into your Angular code at a later point in time.
Step 1: Setting up the initial project
In Visual Studio, create a new ASP.NET MVC4 application with MVC forms and Web API enabled, and configured for Individual Users Accounts.
Test the default app to ensure it runs fine, and the login link in the top right of the page works. If there’s no login link in the upper right corner of the home page, you probably selected Web API instead of MVC as the project type. In that case, go back and recreate the project.
Since we want to keep things simple for now, we'll only use nuget as our package manager and rely on the standard build process. We won’t use bower, npm, gulp, grant or any of the other modern web development tools.
Next we'll use the nuget Package Manager Console (Tools menu) to get the latest stable versions of the following components by entering the following commands:
Install-Package jquery
Install-Package bootstrap
Install-Package angularjs
Install-Package jquery.TypeScript.DefinitelyTyped
Install-Package angularjs.TypeScript.DefinitelyTyped
Install-Package bootstrap.TypeScript.DefinitelyTyped
In my case, the following versions were installed: jquery.2.1.4; bootstrap.3.3.6; angularjs.1.4.8; jquery.TypeScript.DefinitelyTyped 2.3.3; angularjs.TypeScript.DefinitelyTyped.4.4.0; bootstrap.TypeScript.DefinitelyTyped 0.1.7.
In addition to jquery, AngularJS and BootStrap, we loaded the API definitions for those JavaScript libraries which enables Visual Studio to provide IntelliSense as we start building our Angular app using TypeScript.
Now make sure you have the latest stable version of the following tools installed (via Extensions and Updates in the Visual Studio Tools menu).
- TypeScript for Visual Studio. At this time the most recent stable version is 1.7.6.
- SideWaffle Template Pack. This will install TypeScript item templates for various Angular components, such as directives and controllers, which we will user later during this tutorial.
Step 2 - creating "Hello World"
The next step is to create a simple Hello World app using AngularJS and TypeScript. To keep things as simple as possible, we’ll simply use the About page (/Home/About) to host our Angular app.
Create a folder called "App" under the Scripts folder. This is where we'll put the app code.
The HTML page will need to load all the required script files, including the libraries we installed through nuget and all the scripts which will eventually belong to our SPA app. This is a pain if you do it manually, so we'll automate this by adjusting BundleConfig.cs in the App_Start folder by adding the following lines.
bundles.Add(new ScriptBundle("~/bundles/angular").Include("~/Scripts/angular.js"));
bundles.Add(new ScriptBundle("~/bundles/app").IncludeDirectory("~/Scripts/App", "*.js", true));
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/App_Start/BundleConfig.cs
If you wish to use additional Angular components, such as for routing and animations, you can include these in the first bundle statement. The second statement takes all the .js files under our App directory and includes them as well. Note that we'll be writing the code in TypeScript files which have a .ts extension, and TypeScript will transpile them into .js files in the same folder.
Next we'll adjust the _Layout.cshtml in the Views/Shared folder to ensure our bundles are actually loaded in the HTML page. By doing this in the _Layout.cshtml file, any view/page in our MVC project will be able to contain Angular apps.
Add the following lines in the bottom of _Layout.cshtml where the other bundles are also loaded:
@Scripts.Render("~/bundles/angular")
@Scripts.Render("~/bundles/app")
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/Views/Shared/_Layout.cshtml
Now we're ready to create the SPA app itself! First, open About.cshtml in the Views/Home folder and remove everything in it and replace it as follows:
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/Views/Home/About.cshtml
Now we'll create the AngularJS app itself, which initially consists of an Angular module file and an Angular Directive which implements the <my-app> tag.
In the Visual Studio solution explorer, right-click the Scripts/App folder and select Add Item. Create a new file called app.ts using the AngularJs TypeScript Module template under the TypeScript/SideWaffle section.
The default code loads ngAnimate and ngRoute, but we're not using those for now and we'll comment them out to avoid errors. (If you want to use these modules later, you'll need to first install them through the package manager, then update the BundleConfig.cs file to ensure they are loaded on the page and finally add the modules back in app.ts.)
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/Scripts/App/app.ts
Next we add a file called MyAppDirective.ts to the Scripts/App folder, using the AngularJs TypeScript Directive item template. We'll make two modifications: add a template line for the code we wish to generate, and on the last line we'll rename the directive to "myApp", which is how we will wire it up to the "<my-app>" tag in the HTML file we edited before.
The file should look like this: myapp.ts. Later in this tutorial we’ll make more changes to the file.
Now run the app to test that it works. When you navigate to the About page you should see this:
Step 3: adding a Web API
In the Visual Studio solution explorer, add a folder under the Controllers folder called "api". This is where our API controllers will live. Right-click the folder to add an item and add a file called SampleController.cs using the Web API Controller Class (v2.1) template.
The default code already has a sample Get method which returns an array of two string values “value1” and “value2”. This will suffice and we won’t make any further changes to the Web API: SampleController.cs
Check that the controller works by starting the app and then navigate to the https://localhost:<port>/api/sample (keep the port number which is already used for your app). If you're using Internet Explorer, you'll be prompted to download a file. If you open Chrome and navigate to the same URL, you'll see the following:
So now we know our new Web API is working!
Step 4: letting our Angular app consume the Web API
Create an HTML file in the Scripts/App folder called my-app.html with the following contents:
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/Scripts/App/my-app.html
Create a new file under /Scripts/App called MyAppController which you create using the "AngularJs TypeScript Controller using 'Controller As' " item template, and adjust the code as follows: MyAppController.cs.
The controller class has a method called GetValues() which retrieves the data from the Web API REST service and stores it in a variable called “values”. The GetValues() method is called in the constructor, so the data will be loaded as soon as the page loads. The values variable is bound to the HTML template we added before so that the data from the web service is shown on the page.
Now we’ll adjust the MyAppDirective.ts file which we created in step 2 by copying the contents of the following file on GitHub:
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/Scripts/App/MyAppDirective.ts
The MyAppDirective.ts file contains all the plumbing to link the my-app.html template with the controller and map it to the <my-app> tag.
Now run the app and click the About link to verify that the app works:
Adjust the URL on line 21 of MyAppController.ts file to test what happens if the call fails:
As you see the Angular app returns an error message in red when it fails retrieving the data from the Web API.
Change the URL back to the original value.
Step 5: Securing the Web API
One way to secure the app is by decorating the About() method in the HomeController class with the [Authorize] attribute. This method would prevent the About page to be loaded unless the user is logged in. Keep in mind that you also need to secure the Web API controllers otherwise a hacker could still call the Web API directly.
In our app we'll leave the About() method unsecured, but we'll secure the Web API so that users can't access the data, even if they can initially load the About page. Doing this is very simple: just add the [Authorize] attribute to the Web API controller class or method which needs to be secured:
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/Controllers/api/SampleController.cs
We just secured the entire Web API controller class, so let's see what happens when you run the app:
The good news is that we don’t see the data. Security works!
The bad news is that we don't get redirected to the Login page. Even worse, we don't get an error message, which is strange because we built the app such that it displays an error message whenever it fails to get data from the web service. At the very least, we would expect an error message like “Access denied”.
The reason for this behavior is that by default ASP.NET transparently returns the login page instead of a HTTP 401 status code when a user is not authenticated. For a traditional web application which directly renders the HTML it receives from the server this is fine, but in our case the REST service is the one getting back the HTML and it only knows how to parse a JSON response, therefore we don’t see anything.
Fortunately we can modify this default behavior in ASP.NET by removing a single line of code in the Startup.Auth.cs file in the App_Start folder.
Find the following line (probably at line 28)…
LoginPath = new PathString("/Account/Login"),
… and remove it or comment it out.
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/App_Start/Startup.Auth.cs
Now, run our Web application to see what happens when we navigate to the About page:
Yay! We now have an error message!
Step 6: Redirecting to the ASP.NET Login page
Adjust the catch part of getValues() in MyAppController.ts to check for a 401 status code and redirect to the login page:
On GitHub: https://github.com/andreasderuiter/bat-mvc/blob/master/bat-mvc/Scripts/App/MyAppController.ts
Note how we also pass the return URL so that the login page knows where to return back to after the user successfully logged in. You’ll need to add this code to every REST call you make to the Web API in your app (in this boilerplate app this, this is the only place).
That's it! Now, when the Angular app gets a 401 message back, it will redirect the user to the /Account/Login page. Once the user logged in, he or she is redirected back to the About page and subsequent Web API calls return data for as long as the session lasts.
If you implement routing in your Angular application, you can even pass a deep link as returnurl so that the login page will redirect back to a certain “location” in your Angular app.
Conclusion
As you see, ASP.NET works very nicely together with Angular apps. Enabling the solution to leverage all the powerful security functionality in ASP.NET just requires a couple of simple tweaks. I hope you found this useful!