In my last two articles (Security in Angular: Part 1 and Security in Angular: Part 2, you created a set of Angular classes to support user authentication and authorization. You also built a .NET Core Web API project to authenticate a user against a SQL Server table. An authorization object was created with individual properties for each item that you wished to secure in your application. In this article, you're going to build an array of claims and eliminate the use of single properties for each item that you wish to secure. Using an array of claims is a much more flexible approach for large applications.

Security in Angular: Part 1

Security in Angular: Part 2

Download the Starting Application

Download the starting sample for this article from the CODE Magazine website page associated with this article, or from http://pdsa.com/downloads. Select “PDSA/Fairway Articles” from the Category drop-down, and choose “Security in Angular - Part 3.” After you've downloaded the ZIP file, there are two folders within the ZIP entitled “Start” and “End.” Extract all of the files and folders within the “Start” folder to a folder somewhere on your hard drive. You can then follow along with this article to build an array of claims from a UserClaim table.

Load the Visual Studio Code Workspace

After extracting the “Start” folder from the ZIP file, double-click on the PTCApp.code-workspace file to load the two projects in this application. A workspace is loaded that looks like Figure 1. There are two projects: PTC is the Angular application and PtcApi is the ASP.NET Core Web API project.

Figure 1: The starting application has two projects, the Angular project (PTC) and the .NET Core Web API project (PtcApi).
Figure 1: The starting application has two projects, the Angular project (PTC) and the .NET Core Web API project (PtcApi).

The PTC Database

There's a SQL Server Express database named PTC included in the ZIP file. Open the PtcDbContext.cs file located in the \PtcApi\Model folder. Change the path in the connection string constant to point to the folder in which you installed the files from this ZIP file. If you don't have SQL Server Express installed, you can use the PTC.sql file located in the \SqlData folder to create the appropriate tables in your own SQL Server instance.

Security Tables Overview

The PTC database has two tables besides the product and category tables: User and UserClaim (Figure 2). These tables are like the ones you find in the ASP.NET Identity System from Microsoft. I've simplified the structure just to keep the code small for this sample application.

Figure 2: Two security tables are needed to authenticate and authorize a user.
Figure 2: Two security tables are needed to authenticate and authorize a user.

User Table

The user table generally contains information about a specific user such as their user name, password, first name, last name, etc. For the purposes of this article, I've simplified this table to just a unique ID (UserId), the name for the login (UserName), and the Password for the user. Please note that I'm using a plain-text password (Figure 3) in the sample for this application. In a production application, this password would be either encrypted or hashed.

Figure 3: Example data in the User table.
Figure 3: Example data in the User table.

UserClaim Table

In the UserClaim table, there are four fields: ClaimId, UserId, ClaimType, and ClaimValue. The ClaimId is a unique identifier for the claim record. The UserId is a foreign key relation to the User table. The value in the ClaimType field is the one that's used in your Angular application to determine if the user has the appropriate authorization to perform some action. The value in the ClaimValue can be any value you want. I'm using a true or false value for this article.

You don't need to enter a record for a specific user and claim type if you don't wish to give the user that claim. For example, the CanAddProduct property (Figure 4) in the authorization object may either be eliminated for the user bjones, or you can enter a false value for the ClaimValue field. Later in this article, you learn how this process works.

Figure 4: Example data in the UserClaim table.
Figure 4: Example data in the UserClaim table.

Modify Web API Project

Now that you have the database tables configured for your users, you need to modify a few things in the Web API application to support an array of claims. In the previous article, the AppUserAuth class contained a Boolean property for each claim. You tested this Boolean using an Angular *ngIf directive to remove HTML elements from the DOM, thus eliminating the ability for a user to perform some action.

Using individual properties for each claim makes your AppUserAuth class become quite large and unmanageable when you have more than just a few claims. It also means that when you wish to add another claim, you must add a new record to your SQL Server, add a property to the AppUserAuth class in your Web API, add a property to the AppUserAuth class in your Angular application, and add a directive to any DOM element you wish to secure.

Using an array-based approach, you only need to add a record to your SQL Server and add a directive to a DOM element that you wish to secure. This means you have less code to modify, less testing to perform, and thus, your time deploying a new security change decreases.

Modify AppUserAuth Class

Open the AppUserAuth.cs file in the \PtcApi\Model folder and remove each of the individual claim properties you created in the last article. Add a generic list of AppUserClaim objects with the property name of Claims. You need to add a Using statement to import the System.Collections.Generic namespace. You should also initialize the Claims property to an empty list in the constructor of this class. After making these changes, the AppUserAuth class should look like Listing 1.

Listing 1: Modify the AppUserAuth class to use an array of user claims

using System.Collections.Generic;

namespace PtcApi.Model
{
    public class AppUserAuth
    {
        public AppUserAuth()
        {
            UserName = "Not authorized";
            BearerToken = string.Empty;
            Claims = new List<AppUserClaim>();
        }  

        public string UserName { get; set; }
        public string BearerToken { get; set; }
        public bool IsAuthenticated { get; set; }
        public List<AppUserClaim> Claims { get; set; }
    }
}

Modify Security Manager

The SecurityManager.cs file located in the \PtcApi\Model folder is responsible for interacting with the Entity Framework to retrieve security information from your SQL Server tables. Open the SecurityManager.cs file and remove the for loop in the BuildUserAuthObject() method that uses reflection to set property names. The following code snippet is what the BuildUserAuthObject() method should look like after you have made these changes:

protected AppUserAuth BuildUserAuthObject(AppUser authUser)
{
    AppUserAuth ret = new AppUserAuth();

    // Set User Properties
    ret.UserName = authUser.UserName;
    ret.IsAuthenticated = true;
    ret.BearerToken = BuildJwtToken(ret);

    // Get all claims for this user
    ret.Claims = GetUserClaims(authUser);
    return ret;
}

You also need to locate the BuildJWTToken() method and remove the individual properties being set. Each line where these properties are being set should present a syntax error in Visual Studio code because those properties no longer exist.

Modify the Angular Application

As is frequently the case with Angular applications, if you make changes in the Web API project, you need to make changes in the Angular application as well. Let's make those changes now.

Add an AppUserClaim Class

Because you're now going to be returning an array of AppUserClaim objects from the Web API, you need a class named AppUserClaim in your Angular application. Right mouse-click on the \security folder and add a new file named app-user-claim.ts. Add the following code in this file.

export class AppUserClaim  {
    claimId: string = "";
    userId: string = "";
    claimType: string = "";
    claimValue: string = "";
}

Modify the AppUserAuth Class

Open the app-user-auth.ts file and remove all of the individual Boolean claim properties. Just like you removed them from the Web API class, you need to remove them from your Angular application. Next, add an array of AppUserClaim objects to this class, as shown in the following code snippet:

import { AppUserClaim }
  from "./app-user-claim";

export class AppUserAuth {
    userName: string = "";
    bearerToken: string = "";
    isAuthenticated: boolean = false;
    claims: AppUserClaim[] = [];
}

Modify Security Service

Open the security.service.ts file located in the \security folder. Locate the resetSecurityObject() method and remove the individual Boolean properties. Add a line of code to reset the claims array to an empty array of claims, as shown in the following code snippet:

resetSecurityObject(): void {
    this.securityObject.userName = "";
    this.securityObject.bearerToken = "";
    this.securityObject.isAuthenticated = false;
    this.securityObject.claims = [];

    localStorage.removeItem("bearerToken");
}

Claim Validation

Now that you've made code changes on both the server and client sides, the Web API call returns the authorization class with an array of user claims. You now need to be able to check whether a user has a valid claim (authorization) to perform an action or to remove an HTML element from the DOM. You're eventually going to create a custom structural directive that you can use on a menu, as shown here:

<a routerLink="/products"*hasClaim="'canAccessProducts'">Products</a>

To be able to do this, you need a method that takes the string passed to the *hasClaim directive and verifies that this claim exists in the array downloaded from the Web API. This method should also able to check for a claim value set with this claim type. Remember that the ClaimValue field in the SQL Server UserClaim table is of the type string. You can place any value you want into this field. This means that you also want to be able to pass in a value to check, as shown here:

<a routerLink="/products"*hasClaim="'canAccessProducts:false'">Products</a>

Notice the use of a colon, and then the value you want to check for this claim; canAccessProducts:false. This string containing the claim type, a colon, and the claim value is passed to the hasClaim directive. The new method you're going to create should be able to parse this string and determine the claim type and the value (if any). Add this new method to the SecurityService class, and give it the name isClaimValid(), as shown in Listing 2.

Listing 2: Check for a claim type and optionally a claim value in the isClaimValid() method

private isClaimValid(claimType: string):boolean {
    let ret: boolean = false;
    let auth: AppUserAuth = null;
    let claimValue: string = '';

    // Retrieve security object
    auth = this.securityObject;
    if (auth) {
        // See if the claim type has a value *hasClaim="'claimType:value'"
        if (claimType.indexOf(":") >= 0) {
            let words: string[] = claimType.split(":");
            claimType = words[0].toLowerCase();
            claimValue = words[1];
        }
        else {
            claimType = claimType.toLowerCase();
            // Get the claim value, or assume 'true'
            claimValue = claimValue?claimValue:"true";
        }
        // Attempt to find the claim
        ret = auth.claims.find(
            c => c.claimType.toLowerCase()
                == claimType
            && c.claimValue == claimValue)
                != null;
        }

         return ret;
}

The isClaimValid is declared as a private method in the SecurityService class, so you need a public method to call this one. Create a hasClaim() method that looks like the following:

hasClaim(claimType: any) : boolean {
    return this.isClaimValid(claimType);
}

Create Structural Directive to Check Claim

To add your own structural directive, hasClaim, open a terminal window in VS Code and type in the Angular CLI command in this next snippet. This command adds a new directive into the \security folder.

ng g d security/hasClaim --flat

Open the newly created has-claim.directive.ts file and modify the import statement to add a few more classes.

import { Directive, Input, TemplateRef, ViewContainerRef }
from '@angular/core';

Modify the selector property in the @Directive function to read hasClaim.

@Directive({ selector: '[hasClaim]' })

Modify the constructor to inject the TemplateRef, ViewContainerRef, and the SecurityService.

constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private securityService: SecurityService)
{ }

Just like when you bind properties from one element to another, you need to use the Input class to tell Angular to pass the data on the right-hand side of the equal sign in the directive to the hasClaim property in your directive class. Add the following code below the constructor:

@Input() set hasClaim(claimType: any) {
    if (this.securityService.hasClaim(claimType)) {
        // Add template to DOM
        this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
        // Remove template from DOM
        this.viewContainer.clear();
    }
}

The @Input() decorator tells Angular to pass the value on the right-hand side of the equals sign to the set property named hasClaim(). The parameter to the hasClaim property is named claimType. Pass this parameter to the new hasClaim() method you created in the SecurityService class. If this method returns a true, which means that the claim exists, the UI element to which this directive is applied is displayed on the screen using the createEmbeddedView() method. If the claim doesn't exist, the UI element is removed by calling the clear() method on the viewContainer.

Modify Authorization Guard

Just because you remove a menu item doesn't mean that the user can't directly navigate to the path pointed to by the menu. In the first article, you created an Angular guard to stop a user from directly navigating to a route if they didn't have the appropriate claim. As you now verify claims using an array instead of Boolean properties, you need to modify the authorization guard you created. Open the auth.guard.ts file, locate the canActivate() method, and change the if statement to look like the code shown below:

if (this.securityService
        .securityObject.isAuthenticated
            && this.securityService
                .hasClaim(claimName)) {
  return true;
}

Secure Menus

You're just about ready to try out all the changes you made. If you look at the Products and Categories menu items in the app.component.html file, you see that you're using an *ngIf directive to only display menu items if the securityObject property isn't null, and that the Boolean property, canAccessProducts, is set to a true value.

<li>
    <a routerLink="/products"*ngIf="securityObject.canAccessProducts">Products</a>
</li>
<li>
    <a routerLink="/categories"*ngIf="securityObject.canAccessCategories">Categories</a>
</li>

Because the *ngIf directive is bound to the securityObject using two-way data-binding if this property changes, the menus are redrawn. The structural directive you just created passes in a string to a set property that executes code, so there's no binding to an actual property. This means that the menus aren't redrawn if you add the *hasClaim structural as shown previously. Another problem is that you can't have two directives on a single HTML element. Not to worry: You may wrap the two anchor tags within an ng-container and use the *ngIf directive on them to bind to the isAuthenticated property of the securityObject. This property changes once a user logs in, so this allows you to control the visibility of the menus. Then you may use the *hasClaim on the anchor tags to control the visibility based on whether the user's claim is valid. Open the app.component.html file, locate the Products menu item and add the and the *hasClaim directive as shown below:

<li>
    <ng-container*ngIf="securityObject.isAuthenticated">
        <a routerLink="/products"*hasClaim="'canAccessProducts'">Products</a>
    </ng-container>
</li>

Next, locate the Categories menu item and add the and the *hasClaim directive as shown below:

<li>
    <ng-container*ngIf="securityObject.isAuthenticated">
        <a routerLink="/categories"*hasClaim="'canAccessCategories'">Categories</a>
    </ng-container>
</li>

Try it Out

You are finally ready to try out all your changes and verify that your menu items are turned off and on based on the user being authenticated, and that they have the appropriate claims in the UserClaim table. Save all the changes you've made in VS Code. Start the Web API and Angular projects and view the browser. The Products and Categories menus shouldn't be visible. Click on the Login menu and log in using a user name of psheriff and a password of P@ssw0rd. You should now see both menus appear.

Open the User table, locate the bjones user and remember the UserId for this user. Open the UserClaim table, locate the CanAccessCategories record for bjones, and change the value from a true to a false value. Back in the browser, log out as psheriff, and log back in as bjones. You should see the Products menu, but the Categories menu doesn't appear. Go back to the UserClaim table and set the CanAccessCategories claim value field back to a true for bjones.

Secure Add New Product Button

Add the *hasClaim directive to the “Add New Product” button located in the product-list.component.html file. Remove the *ngIf directive that was bound to the old canAddProduct property and use your new structural directive, as shown in the code below:

<button class="btn btn-primary"
        (click)="addProduct()"
        *hasClaim="'canAddProduct'">
    Add New Product
</button>

Don't forget to add the single quotes inside the double quotes. If you forget them, Angular is going to try to bind to a property in your component named canAddProduct, which doesn't exist.

Try It Out

Save all your changes and go back to the browser. Click on the Login menu and log in as psheriff. Click on the Products menu, and you should see the “Add New Product” button appear. Log out as psheriff and login as bjones. The “Add New Product” button should now be gone.

Remember that you added the capability to specify the claim value after the name of the claim. Add a colon after the claim type, then add false to the Add New Product button, as shown below:

<button class="btn btn-primary"
        (click)="addProduct()"
        *hasClaim="'canAddProduct:false'">
    Add New Product
</button>

If you now log in as psheriff, the Add New Product button is gone. Log in as bjones and it should appear. Remove the :false from the claim after you've tested this out.

Add Multiple Claims

Sometimes security requires that you need to secure a UI element using multiple claims. For example, you want to display a button for users that have one claim type and other users that have another claim type. To accomplish this, you need to pass an array of claims to the *hasClaim directive as shown below:

*hasClaim="['canAddProduct',
'canAccessCategories']"

You need to modify the hasClaim() method in the SecurityService class to check to see if a single string value or an array is passed in. Open the security.service.ts file and modify the hasClaim() method to look like Listing 3.

Listing 3: Add the ability to pass multiple claim types to the hasClaim directive

hasClaim(claimType: any) : boolean {
    let ret: boolean = false;

    // See if an array of values was passed in.
    if (typeof claimType === "string") {
        ret = this.isClaimValid(claimType);
    }
    else {
        let claims: string[] = claimType;
        if (claims) {
            for (let index = 0;
            index < claims.length;
            index++) {
                ret = this.isClaimValid(claims[index]);
                // If one is successful, then let them in
                if (ret) {
                    break;
                }
            }
        }
    }

    return ret;
}

As you now have two different data types that can be passed to the hasClaim() method, use the typeof operator to check whether the claimType parameter is a string. If it is, call the isClaimValid() method passing in the two parameters. If it isn't a string, assume it's an array. Cast the claimType parameter into a string array named claims. Verify that it's an array, then loop through each element of the array and pass each element to the isClaimValid() method. If even one claim matches, then return a true from this method so the UI element is displayed.

Secure Other Buttons

Open the product-list.component.html file and modify the “Add New Product” button to use an array, as shown in the following code snippet:

*hasClaim="['canAddProduct',
'canAccessCategories']"

Try It Out

Save all the changes in your application and go back to your browser. Login as bjones and, because he has the canAccessCategories claim, bjones may view the “Add New Product” button.

Summary

In this final article on Angular security, you learned to build a security system that's more appropriate for enterprise type applications. Instead of individual properties for each item that you wish to secure, you return an array of claims from your Web API call. You built a custom structural directive to which you may pass one or more claims. This directive takes care of including or removing an HTML element based on the user's set of claims. This approach makes your code more flexible and it requires less coding changes should you wish to add or delete claims.