Skip to content

Instantly share code, notes, and snippets.

@imzjy
Forked from JaimeStill/README.md
Created March 4, 2019 02:19
Show Gist options
  • Save imzjy/8bba1187f7725f670c926cdd8c0a4d84 to your computer and use it in GitHub Desktop.
Save imzjy/8bba1187f7725f670c926cdd8c0a4d84 to your computer and use it in GitHub Desktop.
Active Directory Authorization Workflow

Active Directory Authorization Workflow

Contents

Overview

In an effort to keep this document relevant to the core subject matter, I will not be going into any detail on Entity Framework, Web API, or Angular. They already have amazing documentation, and I highly encourage you to read up on anything you're not familiar with if this doesn't make sense to you.

ASP.NET Core Docs
Entity Framework Docs
Angular Docs
Angular Material Docs

The intent of this document is to build on the Active Directory Authentication setup provided previously. It will include the following features:

  • Setting up Entity Framework Code First with a custom User entity
  • Building an API around AdUser and User
  • Setting up an Authorization guard for API endpoints
  • Building an Angular service for interacting with the API
  • Setting up an Angular Authentication workflow
  • Building an Angular route guard

This will only feature a simple .IsAdmin boolean property on the User class, but you could extend it to work with a Permission class with a UserPermission join table for a more robust authorization scheme.

Throughout this document, when defining names or path segments that are up to the reader to define, they will be expressed inside of { }. This means to replace the value, to include the brackets, with whatever your value is. For instance, a path of ..\{Project}.Data could be ..\FullstackDemo.Data.

Setup

You'll need to have access to a SQL Server (a SQL Server Express instance is present if you have Visual Studio installed with the Data workload) and work from a machine that's joined to an Active Directory domain.

I have a dotnet new template that serves as good starting point for getting this running on GitHub. Just follow the instructions in the README to install the template.

To create a project, open a command prompt and create / navigate to a directory you want to build the project in, then run dotnet new fullstack. This will create the project with the name of the directory it's hosted in. For example, if the name of the directory is Project, then the directory structure will look as follows:

  • Project.Core
  • Project.Web
  • Project.sln

Alternatively, you can run the command as dotnet new fullstack -n {Project Name} -o {Output Directory}

From the root of the directory you just created, run the following commands:

// Create new projects
{Project}>dotnet new classlib -f netcoreapp2.2 -n {Project}.Data -o {Project}.Data
{Project}>dotnet sln add .\{Project}.Data
{Project}>dotnet add classlib -f netcoreapp2.2 -n {Project}.Identity -o {Project}.Identity
{Project}>dotnet sln add .\{Project}.Identity

// Add Data references
{Project}>cd {Project}.Data
{Project}.Data>dotnet add package Microsoft.EntityFrameworkCore.SqlServer
{Project}.Data>dotnet add package Microsoft.EntityFrameworkCore.Tools
{Project}.Data>dotnet add reference ..\{Project}.Core

// Add Identity references
{Project}.Data> cd ..\{Project}.Identity
{Project}.Identity>dotnet add package Microsoft.AspNetCore.Http
{Project}.Identity>dotnet add package Microsoft.Extensions.Configuration.Abstractions
{Project}.Identity>dotnet add package Microsoft.Extensions.Configuration.Binder
{Project}.Identity>dotnet add package System.DirectoryServices.AccountManagement

// Add Web references
{Project}.Data>cd ..\{Project}.Web
{Project}.Web>dotnet add reference ..\{Project}.Data
{Project}.Web>dotnet add reference ..\{Project}.Identity

Delete both of the Class1.cs files out of both the {Project}.Data and {Project}.Identity classes.

At this point, go ahead and build out all of the infrastructure for {Project}.Identity as defined in the Active Directory Authentication document. Note that launchSettings.json is located at {Project}.Web\Properties\launchSettings.json.

Entity Framework Setup

{Project}.Web\appsettings.Development.json already contains a connection string. The only modification you should need to make is to update the value of Server={instance} to match the instance of the SQL Server you'll be using. (localdb)\\ProjectsV13 is installed by default if you have SQL Server Express installed from Visual Studio.

Create the following directory structure in {Project}.Data:

  • Entities
  • Extensions
  • AppDbContext.cs

{Project}.Data\Entities\User.cs

The SocketName property is relevant for when using SignalR to send direct messages to users in a Hub.

namespace Project.Data.Entities
{
    public int Id { get; set; }
    public Guid Guid { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
    public string SocketName { get; set; }
    public string Theme { get; set; }
    public bool IsDeleted { get; set; }
    public bool IsAdmin { get; set; }
}

{Project}.Data\AppDbContext.cs

namespace Project.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
        
        public DbSet<User> Users { get; set; }
        
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            /*
            * This causes table names in SQL Server to be their singular class name
            * as opposed to the plural DbSet<T> name.
            * 
            * In this case, the table name will be User instead of Users.
            */
            modelBuilder
                .Model
                .GetEntityTypes()
                .ToList()
                .ForEach(x =>
                {
                    modelBuilder
                        .Entity(x.Name)
                        .ToTable(x.Name.Split('.').Last());
                });
        }
    }
}

{Project}.Data\Extensions\IdentityExtensions.cs

namespace Project.Data.Extensions.IdentityExtensions
{
    public static async Task<List<User>> GetUsers(this AppDbContext db, bool isDeleted = false)
    {
        var users = await db
            .Users
            .Where(x => x.IsDeleted == isDeleted)
            .OrderBy(x => x.LastName)
            .ToListAsync();
            
        return users;
    }
    
    public static async Task<List<User>> SearchUsers(this AppDbContext db, string search, bool isDeleted = false)
    {
        search = search.ToLower();
        
        var users = await db
            .Users
            .Where(x => x.IsDeleted == isDeleted)
            .Where(x =>
                x.Email.ToLower().Contains(search) ||
                x.FirstName.ToLower().Contains(search) ||
                x.LastName.ToLower().Contains(search) ||
                x.Username.ToLower().Contains(search)
            )
            .OrderBy(x => x.LastName)
            .ToListAsync();
            
        return users;
    }
    
    public static async Task<User> GetUser(this AppDbContext db, int id)
    {
        var user = await db.Users.FindAsync(id);
        return user;
    }
    
    public static async Task<User> SyncUser(this AdUser adUser, AppDbContext db)
    {
        var user = await db
            .Users
            .FirstOrDefaultAsync(x => x.Guid == adUser.Guid);
            
        user = user == null ?
            await db.AddUser(adUser) :
            await db.UpdateUser(adUser);
            
        return user;
    }
    
    public static async Task<User> AddUser(this AppDbContext db, AdUser adUser)
    {
        User user = null;
        
        if (await adUser.Validate(db))
        {
            user = new User
            {
                Email = adUser.UserPrincipalName,
                FirstName = adUser.GivenName,
                Guid = adUser.Guid.Value,
                IsDeleted = false,
                LastName = adUser.Surname,
                SocketName = $@"{adUser.GetDomainPrefix()}\{adUser.SamAccountName}",
                Theme = "dark-green",
                Username = adUser.SamAccountName
            }
            
            await db.Users.AddAsync(user);
            await db.SaveChangesAsync();
        }
        
        return user;
    }
    
    public static async Task UpdateUser(this AppDbContext db, User user)
    {
        db.Users.Update(user);
        await db.SaveChangesAsync();
    }
    
    private static async Task<User> UpdateUser(this AppDbContext db, AdUser adUser)
    {
        var user = await db.Users.FirstOrDefaultAsync(x => x.Guid == adUser.Guid);
        
        user.Email = adUser.UserPrincipalName;
        user.FirstName = adUser.GivenName;
        user.LastName = adUser.Surname;
        user.SocketName = $@"{adUser.GetDomainPrefix()}\{adUser.SamAccountName}";
        user.Username = adUser.SamAccountName;
        
        await db.SaveChangesAsync();
        
        return user;
    }
    
    public static async Task ToggleUserDeleted(this AppDbContext db, User user)
    {
        db.Users.Attach(user);
        user.IsDeleted = !user.IsDeleted;
        await db.SaveChangesAsync();
    }

    public static async Task ToggleAdminUser(this AppDbContext db, User user)
    {
        db.Users.Attach(user);
        user.IsAdmin = !user.IsAdmin;
        await db.SaveChangesAsync();
    }
    
    public static async Task RemoveUser(this AppDbContext db, User user)
    {
        db.Users.Remove(user);
        await db.SaveChangesAsync();
    }
    
    public static async Task<bool> Validate(this AdUser user, AppDbContext db)
    {
        var check = await db
            .Users
            .FirstOrDefaultAsync(x => x.Guid == user.Guid.Value);
            
        if (check != null)
        {
            throw new Exception("The provided user already has an account");
        }
        
        return true;
    }
}

AppDbContext needs to be configured and registered as a service in {Project}.Web\Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .AddJsonOptions(options =>
        {
            options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        });

    services.AddDbContext<AppDbContext>(options =>
    {
        options.UseSqlServer(Configuration.GetConnectionString("Dev"));
        options.EnableSensitiveDataLogging();
    });

    // Additional Configuration
}

Now all that's left for Entity Framework is to add a migration and update the database. From a command prompt pointed at {Project}.Data, run the following commands:

{Project}.Data>dotnet ef migrations add initial -s ..\Project.Web
{Project}.Data>dotnet ef database update -s ..\Project.Web

API Setup

In {Project}.Web, create a folder named Controllers and add a file named IdentityController.cs.

{Project}.Web\Controllers\IdentityController.cs

namespace Project.Web.Controllers
{
    [Route("api/[controller]")]
    public class IdentityController : Controller
    {
        private IUserProvider provider;
        private AppDbContext db;
        private readonly string appGroup;
        
        public IdentityController(IUserProvider provider, AppDbContext db, IConfiguration config)
        {
            this.provider = provider;
            this.db = db;
            appGroup = config.GetValue<string>("AppAdGroup");
        }
        
        [HttpGet("[action]")]
        public async Task<List<AdUser>> GetDomainUsers() => await provider.GetDomainUsers();
        
        [HttpGet("[action]/{search}")]
        public async Task<List<AdUser>> FindDomainUser([FromRoute]string search) => await provider.FindDomainUser(search);
        
        [HttpGet("[action]")]
        public async Task<List<User>> GetUsers() => await db.GetUsers();
        
        [HttpGet("[action]")]
        public async Task<List<User>> GetDeletedUsers() => await db.GetUsers(true);
        
        [HttpGet("[action]/{search}")]
        public async Task<List<User>> SearchUsers([FromRoute]string search) => await db.SearchUsers(search);
        
        [HttpGet("[action]/{search}")]
        public async Task<List<User>> SearchDeletedUsers([FromRoute]string search) => await db.SearchUsers(search, true);
        
        [HttpGet("[action]/{id}")]
        public async Task<User> GetUser([FromRoute]int id) => await db.GetUser(id);
        
        [HttpGet("[action]")]
        public async Task<User> SyncUser() => await provider.CurrentUser.SyncUser(db);
        
        [HttpPost("[action]")]
        public async Task AddUser([FromBody]AdUser adUser) => await db.AddUser(adUser);
        
        [HttpPost("[action]")]
        public async Task UpdateUser([FromBody]User user) => await db.UpdateUser(user);
        
        [HttpPost("[action]")]
        public async Task ToggleUserDeleted([FromBody]User user) => await db.ToggleUserDeleted(user);

        [HttpPost("[action]")]
        public async Task ToggleAdminUser([FromBody]User user) => await db.ToggleAdminUser(user);
        
        [HttpPost("[action]")]
        public async Task RemoveUser([FromBody]User user) => await db.RemoveUser(user);
    }
}

This controller allows you to call the above messages and return data as JSON (where relevant) over HTTP. For instance, http://{localhost:5000}/api/identity/findDomainUser/jaime will return all users in the domain who have jaime as part of their UserPrincipal.SamAccountName. You can also POST JSON data to any of the POST methods, and it will be serialized to the appropriate C# object type (so long as the JSON signature matches the signature of the C# object).

API Route Guards

Create a folder in {Project}.Web named Authorization and add the following files:

  • AdminRequirement.cs
  • AdminAuthorizationHandler.cs

{Project}.Web\Authorization\AdminRequirement.cs

using AccountManager.Data;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Project.Web.Authorization
{
    public class AdminRequirement : IAuthorizationRequirement
    {
    }
}

{Project}.Web\Authorization\AdminAuthorizationHandler.cs

using Microsoft.AspNetCore.Authorization;
using Project.Data.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Project.Web.Authorization
{
    public class AdminAuthorizationHandler : AuthorizationHandler<AdminRequirement>
    {
        AppUser user;

        public AdminAuthorizationHandler(UserManager manager)
        {
            user = manager.CurrentUser;
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement)
        {
            if (user.IsAdmin)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }

            return Task.CompletedTask;
        }
    }
}

Now, Authorization needs to be configured in {Project}.Web\Startup.cs:

{Project}.Web\Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // Additional Configuration

    services.AddAuthorization(options =>
    {
        options.AddPolicy("IsAdmin", policy => policy.Requirements.Add(new AdminRequirement());
    });

    services.AddScoped<IAuthorizationHandler, AdminAuthorizationHandler>();

    // Additional Configuration
}

With this infrastructure in place, you can now use the [Authorize] attribute in conjunction with this new AdminAuthorizationHandler.

[Authorize(Policy = "IsAdmin")]
[HttpPost("[action]")]
public async Task ToggleAdminUser([FromBody]User user) => await db.ToggleAdminUser(user);

If the currently logged in user is not an admin, they will not be able to execute the ToggleAdminUser API route.

Angular Service

There's a folder convention that I use in Angular that makes managing / referencing each facet of the app much easier. Inside of the {Project}.Web\ClientApp\src\app folder, there is a folder for each Angular structure that contains an index.ts file. Any file contained within a folder must reference another file contained in the folder directly, or it will create a circular dependency loop. However, any file contained outside of this folder can reference files using the folder index.

For instance, inside of a service, you can reference multiple models in one import statement:

import { AdUser, User } from '../models';

However, a model must reference another model directly: import { AdUser} from './ad-user';

There are more benefits to this setup, especially concerning module configuration. If interested, take a look at the index.ts file in each folder, then take a look at both app.module.ts and services.module.ts to see how this setup is used.

In order to create the service, we need to have TypeScript types that will map to the C# types it will interact with.

{Project}.Web/ClientApp/src/app/models/ad-user.ts

export class AdUser {
  accountExpirationDate: Date;
  accountLockoutTime: Date;
  badLogonCount: number;
  description: string;
  displayName: string;
  distinguisedName: string;
  emailAddress: string;
  employeeId: string;
  enabled: boolean;
  givenName: string;
  guid: string;
  homeDirectory: string;
  homeDrive: string;
  lastBadPasswordAttempt: Date;
  lastLogon: Date;
  lastPasswordSet: Date;
  middleName: string;
  name: string;
  passwordNeverExpires: boolean;
  passwordNotRequired: boolean;
  samAccountName: string;
  scriptPath: string;
  sid: string;
  surname: string;
  userCannotChangePassword: boolean;
  userPrincipalName: string;
  voiceTelephoneNumber: string;
}

{Project}.Web\ClientApp\src\app\models\user.ts

export class User {
  id: number;
  guid: string;
  firstName: string;
  lastName: string;
  username: string;
  email: string;
  socketName: string;
  theme: string;
  isDeleted: boolean;
  isAdmin: boolean;
}

{Project}.Web\ClientApp\src\app\models\index.ts

export * from './ad-user';
export * from './user';

With the models defined, we can now build out the Angular service:

{Project}.Web\ClientApp\src\app\services\identity.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { SnackerService } from './snacker.service';

import {
  AdUser,
  User
} from '../models';

@Injectable()
export class IdentityService {
  private domainUsers = new BehaviorSubject<AdUser[]>(null);
  private userGroups = new BehaviorSubject<string[]>(null);
  private users = new BehaviorSubject<User[]>(null);
  private user = new BehaviorSubject<User>(null);
  private currentUser = new BehaviorSubject<User>(null);
  
    
  domainUsers$ = this.domainUsers.asObservable();
  userGroups$ = this.userGroups.asObservable();
  users$ = this.users.asObservable();
  user$ = this.user.asObservable();
  currentUser$ = this.currentUser.asObservable();
    
  constructor(
    private http: HttpClient,
    private snacker: SnackerService
  ) { }
    
  getDomainUsers = () =>
    this.http.get<AdUser[]>('/api/identity/getDomainUsers')
      .subscribe(
        data => this.domainUsers.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  findDomainUser = (search: string) =>
    this.http.get<AdUser[]>(`/api/identity/findDomainUser/${search}`)
      .subscribe(
        data => this.domainUsers.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  getUsers = () =>
    this.http.get<User[]>('/api/identity/getUsers')
      .subscribe(
        data => this.users.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  getDeletedUsers = () =>
    this.http.get<User[]>('/api/identity/getDeletedUsers')
      .subscribe(
        data => this.users.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  searchUsers = (search: string) =>
    this.http.get<User[]>(`/api/identity/searchUsers/${search}`)
      .subscribe(
        data => this.users.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  searchDeletedUsers = (search: string) =>
    this.http.get<User[]>(`/api/identity/searchDeletedUsers/${search}`)
      .subscribe(
        data => this.users.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  getUser = (id: number) =>
    this.http.get<User>(`/api/identity/getUser/${id}`)
      .subscribe(
        data => this.user.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  syncUser = () =>
    this.http.get<User>('/api/identity/syncUser')
      .subscribe(
        data => this.currentUser.next(data),
        err => this.snacker.sendErrorMessage(err.error)
      );
  
  addUser = (user: AdUser): Promise<boolean> =>
    new Promise<boolean>((resolve) => {
      this.http.post('/api/identity/addUser', user)
        .subscribe(
          () => {
            this.snacker.sendSuccessMessage(`Account created for ${user.samAccountName}`);
            resolve(true);
          },
          err => {
            this.snacker.sendErrorMessage(err.error);
            resolve(false);
          }
        )
    });
  
  updateUser = (user: User): Promise<boolean> =>
    new Promise<boolean>((resolve) => {
      this.http.post('/api/identity/updateUser', user)
        .subscribe(
          () => {
            this.snacker.sendSuccessMessage(`${user.username} successfully updated`);
            resolve(true);
          },
          err => {
            this.snacker.sendErrorMessage(err.error);
            resolve(false);
          }
        )
    });
  
  toggleUserDeleted = (user: User): Promise<boolean> =>
    new Promise<boolean>((resolve) => {
      this.http.post('/api/identity/toggleUserDeleted', user)
        .subscribe(
          () => {
            const message = user.isDeleted ?
              `${user.username} successfully restored` :
              `${user.username} successfully deleted`;
            
            this.snacker.sendSuccessMessage(message);
            resolve(true);
          },
          err => {
            this.snacker.sendErrorMessage(err.error);
            resolve(false);
          }
        )
    });

  toggleAdminUser = (user: User): Promise<boolean> =>
    new Promise<boolean>((resolve) => {
      this.http.post('/api/identity/toggleAdminUser', user)
        .subscribe(
          () => {
            const message = user.isAdmin ?
              `Permissions removed from ${user.username}` :
              `Permissions granted to ${user.username}`;
            
            this.snacker.sendSuccessMessage(message);
            resolve(true);
          },
          err => {
            this.snacker.sendErrorMessage(err.error);
            resolve(false);
          }
        )
    });
  
  removeUser = (user: User): Promise<boolean> =>
    new Promise<boolean>((resolve) => {
      this.http.post('/api/identity/removeUser', user)
        .subscribe(
          () => {
            this.snacker.sendSuccessMessage(`${user.username} permanently deleted`);
            resolve(true);
          },
          err => {
            this.snacker.sendErrorMessage(err.error);
            resolve(false);
          }
        )
    });
}

In this service, each API endpoint is defined as a function. For GET requests, the results are pushed into an Observable stream via a private BehaviorSubject<T>, which is exposed to the app as a read-only Observable. For POST requests, the HTTP call is wrapped in a Promise to enable async / await to be used by the caller and for any subsequent actions to be performed once the transaction is complete.

Angular Authentication Workflow

With all of this complete, we can now sync the current Active Directory user to our User database table and keep track of them in the Angular app.

All we need to do is the following in the AppComponent:

{Project}.Web\ClientApp\src\app\app.component.ts

import { Theme } from './models';
import { IdentityService } from './services';

import {
  Component,
  OnInit
} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  providers: [ IdentityService ]
})
export class AppComponent implements OnInit {
  constructor(
    public identity: IdentityService
  ) { }

  ngOnInit() {
    this.identity.syncUser();
  }
}

When the component initializes, the identity service will call the syncUser() function. This will check to see if the current IIdentity on the HttpContext has an account in the database. If not, it will create a new account based on the UserPrincipal that is retrieved from the IIdentity instance. If the user does have an account, it will update their properties in the event that anything changed from the last time they logged in (for instance, someone was married and their name changed).

We can then subscribe to the currentUser$ Observable in the component template. We will only render the app if the current user is logged in via Active Directory.

<div class="mat-typography mat-app-background app-panel"
     fxLayout="column"
     *ngIf="identity.currentUser$ | async as user else loading">
  <mat-toolbar color="primary">
    <span fxFlex>Title</span>
    <span>Hello, {{user.username}}</span>
  </mat-toolbar>
  <section class="app-body">
    <router-outlet></router-outlet>
  </section>
</div>
<ng-template #loading>
  <mat-progress-bar mode="indeterminate"
                    color="secondary"></mat-progress-bar>
</ng-template>

Angular Route Guards

Now that we have access to the current user, we can build a route guard to prevent non-admins from accessing certain areas of the app.

In the {Project}.Web\ClientApp\src\app directory, create a guards folder with a file name auth-guard.ts:

{Project}.Web\ClientApp\src\app\guards\auth-guard.ts

import { Injectable } from '@angular/core';

import {
  CanActivate,
  Router,
  ActivatedRouteSnapshot,
  RouterStateSnapshot
} from '@angular/router';

import { IdentityService } from '../services';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor (
    private identity: IdentityService,
    private router: Router
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    return this.checkLogin();
  }

  checkLogin(): boolean {
    if (this.identity.currentUser.value.isAdmin) { return true; }
    this.router.navigate(['/home']);
    return false;
  }
}

Create an index.ts file in the guards folder:

{Project}.Web\ClientApp\src\app\guards\index.ts

import { AuthGuard } from './auth-guard';

export const Guards = [
  AuthGuard
];

export * from './auth-guard';

Expand the Guards array into the providers array in services.module.ts:

{Project}.Web\ClientApp\src\app\services.module.ts

import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { Guards } from './guards';
import { Services } from './services';
import { Pipes } from './pipes';

@NgModule({
  providers: [
    [...Services],
    [...Guards]
  ],
  declarations: [
    [...Pipes]
  ],
  imports: [
    HttpClientModule
  ],
  exports: [
    [...Pipes],
    HttpClientModule
  ]
})
export class ServicesModule { }

Now, all you have to do in order to lock down a route in Angular is add the canActivate property to a route (as defined in {Project}.Web\ClientApp\src\app\routes\index.ts) as follows:

import { AuthGuard } from '../guards';

export const Routes: Route[] = [
  { path: 'admin', component: AdminComponent, canActive: [AuthGuard] },
  // additional routes...
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment