Modern web applications have an explicit separation between the server and the client. Clients use AngularJS, ReactJS, EmberJS, and others. Servers use NodeJS, Java, and, .NET. Microsoft’s .NET platform is a strong, battle-proven server-side framework, and AngularJS is arguably the most popular client-side framework. They work seamlessly together, but adding solid, secure authentication can seem daunting. Leveraging Stormpath makes it easier than you’d think!

Get the Base Application

If you want to follow along with this article, you can clone the master branch from Github or download it from here. If you just want to see the finished project, you can clone the finished branch, or download the finished app from here.

1. Get Your Stormpath Credentials

Login to your Stormpath Account and there is a link to Manage API Keys under Developer Tools on the right-hand side of the admin home page (If this is a new Stormpath account, the link will say “Create API Key”). At the bottom of the page there is an API Keys section, just click the “Create API Key” button. After confirming with the modal dialog, a file named apiKey-[APIKEYID].properties should automatically be downloaded to your computer. Hang on to that, and we’ll use it later.

2. Add Stormpath to Your ASP.NET Core WebAPI

Visual Studio 2015

Open the Visual Studio Package Manager console and enter the following command:

PM> Install-Package Stormpath.AspNetCore 1 2 PM > Install - Package Stormpath . AspNetCore

Visual Studio Code

If you aren’t using Visual Studio, you can also edit project.json file add the following line to the dependencies section:

"Stormpath.AspNetCore": "*" 1 2 "Stormpath.AspNetCore" : "*"

Then run dotnet restore (Chances are, VS Code will prompt you with a “Restore” button at the top of the IDE).

Adding the Stormpath Middleware

Open the Startup.cs file in the root of your project. This file is required for ASP.NET Core applications and is similar to Global.asax file in ASP.NET. At the top of the file, add this using statement:

using Stormpath.AspNetCore; 1 2 using Stormpath . AspNetCore ;

Then, edit the ConfigureServices and Configure methods as follows:

public void ConfigureServices(IServiceCollection services) { services.AddStormpath(); // Add framework services. services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // This is required to serve static files like .html or .js app.UseStaticFiles(); // make sure this line comes after app.UseStaticFiles(); app.UseDefaultFiles(); // serves up our wwwroot files app.UseStaticFiles(); // allows serving of static files app.UseStormpath(); app.UseMvc(); // sets MVC routes for webapi } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void ConfigureServices ( IServiceCollection services ) { services . AddStormpath ( ) ; // Add framework services. services . AddMvc ( ) ; } public void Configure ( IApplicationBuilder app , IHostingEnvironment env ) { // This is required to serve static files like .html or .js app . UseStaticFiles ( ) ; // make sure this line comes after app.UseStaticFiles(); app . UseDefaultFiles ( ) ; // serves up our wwwroot files app . UseStaticFiles ( ) ; // allows serving of static files app . UseStormpath ( ) ; app . UseMvc ( ) ; // sets MVC routes for webapi }

3. Add Your Stormpath Credentials to Your Application

Now we’ll install our Stormpath credentials into the application. Create a file in the root of the application and name it stormpath.yaml . Use the API key and secret contained in the apiKey.properties file you downloaded, as well as the Stormpath Application href to fill in the values in the YAML file:

--- application: href: "https://api.stormpath.com/v1/applications/<application_id>" client: apiKey: id: "<id_found_in_file>" secret: "<secret_found_in_file>" 1 2 3 4 5 6 7 8 9 --- application : href : "https://api.stormpath.com/v1/applications/<application_id>" client : apiKey : id : "<id_found_in_file>" secret : "<secret_found_in_file>"

Configuration via a YAML file is simple and straightforward, but it’s important to not check this file into public source control, as it would expose your API key and secret. For production, Stormpath strongly recommends you to store these values as environment variables, STORMPATH_APPLICATION_HREF , STORMPATH_CLIENT_APIKEY_ID and STORMPATH_CLIENT_APIKEY_SECRET respectively. Read the documentation on Environment Variables here.

4. Add Stormpath to the AngularJS App

You can use your package manager of choice (Bower or NPM) for client-side dependencies. For simplicity’s sake, we’re going to get the reference directly from Github using Rawgit.

Add a link reference in your index.html file in the root of your client app to the Stormpath Angular SDK and templates files.

<script src="//cdn.rawgit.com/stormpath/stormpath-sdk-angularjs/1.0.0/dist/stormpath-sdk-angularjs.min.js"></script> <script src="//cdn.rawgit.com/stormpath/stormpath-sdk-angularjs/1.0.0/dist/stormpath-sdk-angularjs.tpls.min.js"></script> 1 2 3 <script src = "//cdn.rawgit.com/stormpath/stormpath-sdk-angularjs/1.0.0/dist/stormpath-sdk-angularjs.min.js" > </script> <script src = "//cdn.rawgit.com/stormpath/stormpath-sdk-angularjs/1.0.0/dist/stormpath-sdk-angularjs.tpls.min.js" > </script>

Then add the references to the angular app module:

angular.module('ToDoApp', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.router', 'stormpath', 'stormpath.templates']) 1 2 angular . module ( 'ToDoApp' , [ 'ngCookies' , 'ngResource' , 'ngSanitize' , 'ui.router' , 'stormpath' , 'stormpath.templates' ] )

5. Add Login and Register to the Angular App

Next, we’ll add some menu items in the index.html file:

<li><a ui-sref="login" if-not-user>Login</a></li> <li><a ui-sref="register" if-not-user>Register</a></li> <li><a ui-sref="home" sp-logout if-user>Logout</a></li> 1 2 3 4 < li > < a ui - sref = "login" if - not - user > Login < / a > < / li > < li > < a ui - sref = "register" if - not - user > Register < / a > < / li > < li > < a ui - sref = "home" sp - logout if - user > Logout < / a > < / li >

And the routes that go with them:

.state('register', { url: '/register', templateUrl: '/app/auth/views/register.view.html' }) .state('login', { url: '/login', templateUrl: '/app/auth/views/login.view.html' }) .state('todo', { url: '/todo', templateUrl: '/app/todo/views/todo.view.html', sp: { authenticate: true } }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 . state ( 'register' , { url : '/register' , templateUrl : '/app/auth/views/register.view.html' } ) . state ( 'login' , { url : '/login' , templateUrl : '/app/auth/views/login.view.html' } ) . state ( 'todo' , { url : '/todo' , templateUrl : '/app/todo/views/todo.view.html' , sp : { authenticate : true } } )

We’ve added a login and register route to the application’s router, and we’ve updated the todo route to only be accessible by logged-in users.

The “Logout” route is completely handled by Stormpath, but we’ll need some handlers for the “Login” and “Register” routes. I’ve created a folder called auth to put these controllers in. First, the controllers:

login.controller.js

(function(){ function loginController(){} angular.module('ToDoApp') .controller('LoginController', [loginController]); }()) 1 2 3 4 5 6 7 ( function ( ) { function loginController ( ) { } angular . module ( 'ToDoApp' ) . controller ( 'LoginController' , [ loginController ] ) ; } ( ) )

register.controller.js

(function(){ function registerController(){} angular.module('ToDoApp') .controller('RegisterController', [registerController]); }()) 1 2 3 4 5 6 7 ( function ( ) { function registerController ( ) { } angular . module ( 'ToDoApp' ) . controller ( 'RegisterController' , [ registerController ] ) ; } ( ) )

Also, don’t forget to at the script references to your index.html page:

<script src="app/auth/controllers/login.controller.js"></script> <script src="app/auth/controllers/register.controller.js"></script> 1 2 3 <script src = "app/auth/controllers/login.controller.js" > </script> <script src = "app/auth/controllers/register.controller.js" > </script>

You’ll notice, both controllers are essentially empty. That’s because the real magic happens in the views:

login.view.html

<section ng-controller="LoginController"> <div sp-login-form></div> </section> 1 2 3 4 < section ng - controller = "LoginController" > < div sp - login - form > < / div > < / section >

register.view.html

<section ng-controller="RegisterController"> <div sp-registration-form post-login-state="todo"></div> </section> 1 2 3 4 < section ng - controller = "RegisterController" > < div sp - registration - form post - login - state = "todo" > < / div > < / section >

We used a few directives here that come with the Stormpath Angular SDK. The sp-login-form and sp-registration-form create their respective forms, and the post-login-state tells the application where to go once the registration process is done and the application logs us in (assuming auto-login is enabled in the Stormpath Management UI).

6. Configure the Angular App

There are some things that need to be configured to make the Angular/.NET/Stormpath combination work seamlessly. First, we need to set up the routing hash and the configuration for the registration form’s POST behavior:

(Don’t forget to inject the $locationProvider and STORMPATH_CONFIG into the config function for the application)

$locationProvider.html5Mode(true); STORMPATH_CONFIG.FORM_CONTENT_TYPE = 'application/json'; 1 2 3 $ locationProvider . html5Mode ( true ) ; STORMPATH_CONFIG . FORM_CONTENT_TYPE = 'application/json' ;

For html5Mode to work in Angular, you must specify the base URL in the index.html file:

<head> <meta charset="utf-8"> <base href="/"> <!-- This tells the application what the base url is for the app --> 1 2 3 4 < head > < meta charset = "utf-8" > < base href = "/" > < ! -- This tells the application what the base url is for the app -- >

This turns on HTML5-type routing for the Angular application, and the STORMPATH_CONFIG line tells the Angular SDK to POST the registration form as application/json . By default, the Angular SDK sends the registration information as application/x-www-form-urlencoded , but the ASP.NET SDK expects JSON to be posted. This line just makes sure they’re on the same page as far as Content-Type headers.

Lastly, we’ll configure how the application behaves after login and after logout:

.run(['$stormpath', '$rootScope', '$state', initializer]); 1 2 . run ( [ '$stormpath' , '$rootScope' , '$state' , initializer ] ) ;

function initializer($stormpath, $rootScope, $state) { // Finally, configure the login state and the default state after login $stormpath.uiRouter({ loginState: 'login', defaultPostLoginState: 'todo' }); // Bind the logout event $rootScope.$on('$sessionEnd', function () { $state.transitionTo('login'); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 function initializer ( $ stormpath , $ rootScope , $ state ) { // Finally, configure the login state and the default state after login $ stormpath . uiRouter ( { loginState : 'login' , defaultPostLoginState : 'todo' } ) ; // Bind the logout event $ rootScope . $ on ( '$sessionEnd' , function ( ) { $ state . transitionTo ( 'login' ) ; } ) ; }

This code lets the application know that we want users to be routed to the todo route once someone has successfully logged in and that we want them to be routed to the login routed when they log out.

7. Adding Authorization to the Server Side

Now that we have the client side authenticating users, we need to make sure the server side only returns ToDos for the currently logged in user. First, we need to make sure that when the user requests their ToDos, they’re authenticated by adding the Authorize attribute to the TodoController :

[Authorize] [Route("api/[controller]")] public class TodosController : Controller { // implementation ... } 1 2 3 4 5 6 7 [ Authorize ] [ Route ( "api/[controller]" ) ] public class TodosController : Controller { // implementation ... }

Then we just make sure the queries filter by the currently logged in user and attach the current user to our ToDos when they’re being added.

[HttpGet] public IActionResult Get() { return Ok(_context.Todos.Where(x=>x.User == User.Identity.Name).ToList()); } [HttpGet("{id}", Name = "GetTodo")] public IActionResult GetById(int id) { var todo = _context.Todos.SingleOrDefault(t => t.User == User.Identity.Name && t.Id == id); if(todo == null) { return NotFound($"No todo with an Id of {id} was found."); } return Ok(todo); } public IActionResult Post([FromBody] Todo todo) { if (string.IsNullOrEmpty(todo.Description)) { return BadRequest("There must be a description in the todo."); } todo.User = User.Identity.Name; _context.Entry(todo).State = todo.Id > 0 ? EntityState.Modified : EntityState.Added; _context.SaveChanges(); return CreatedAtRoute("GetTodo", new { id = todo.Id }, todo); } [HttpDelete("{id}")] public IActionResult Delete(int id) { var todo = _context.Todos.FirstOrDefault(t => t.User == User.Identity.Name && t.Id == id); if (todo == null) { return NotFound($"No todo with an Id of {id} was found for the current user."); } _context.Todos.Remove(todo); _context.SaveChanges(); return Ok(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 [ HttpGet ] public IActionResult Get ( ) { return Ok ( _context . Todos . Where ( x = > x . User == User . Identity . Name ) . ToList ( ) ) ; } [ HttpGet ( "{id}" , Name = "GetTodo" ) ] public IActionResult GetById ( int id ) { var todo = _context . Todos . SingleOrDefault ( t = > t . User == User . Identity . Name && t.Id == id); if ( todo == null ) { return NotFound ( $ "No todo with an Id of {id} was found." ) ; } return Ok ( todo ) ; } public IActionResult Post ( [ FromBody ] Todo todo ) { if ( string . IsNullOrEmpty ( todo . Description ) ) { return BadRequest ( "There must be a description in the todo." ) ; } todo . User = User . Identity . Name ; _context . Entry ( todo ) . State = todo . Id > 0 ? EntityState . Modified : EntityState . Added ; _context . SaveChanges ( ) ; return CreatedAtRoute ( "GetTodo" , new { id = todo . Id } , todo ) ; } [ HttpDelete ( "{id}" ) ] public IActionResult Delete ( int id ) { var todo = _context . Todos . FirstOrDefault ( t = > t . User == User . Identity . Name && t.Id == id); if ( todo == null ) { return NotFound ( $ "No todo with an Id of {id} was found for the current user." ) ; } _context . Todos . Remove ( todo ) ; _context . SaveChanges ( ) ; return Ok ( ) ; }

That’s it! When you fire up the application and try to navigate to the todo page, you should be redirected to the login route, and it should look something like this:

You should now only see ToDos for the currently logged in user, and when you add a ToDo, it should be saved as a ToDo for that user!

—

Excited to learn more about ASP.NET Core, or user authentication with Stormpath? Check out these resources:

And as always, hit me up in the comments below, or on Twitter @leebrandt with questions!