← Back to Articles         
How do you secure sensitive routes?

How do you secure sensitive routes?

Published on 02 Oct 2026     9 min read Angular
Security

How Do You Secure Sensitive Routes in Angular?

In an Angular + ASP.NET Core application, sensitive routes such as:

/admin
/admin/users
/dashboard
/profile
/orders

should be protected at two levels:

Angular Route Guard
        +
ASP.NET Core Authorization

The most important principle is:

Angular route guards improve client-side navigation and UX, but ASP.NET Core must enforce the real security.

A user can bypass Angular and call your API directly using Postman, curl, or another client.


1. Full Simple Sample Program

Suppose we have:

  • /login → public
  • /dashboard → authenticated users
  • /admin → Admin role only

auth.service.ts

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

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  isLoggedIn = signal(false);

  role = signal<string | null>(null);

  login(role: string): void {
    this.isLoggedIn.set(true);
    this.role.set(role);
  }

  logout(): void {
    this.isLoggedIn.set(false);
    this.role.set(null);
  }
}

For simplicity, this demo keeps authentication state in signals. In a real application, the state should reflect your actual authenticated session/token and backend responses.


2. Authentication Guard

auth.guard.ts

import { inject } from '@angular/core';
import {
  CanActivateFn,
  Router
} from '@angular/router';

import { AuthService } from './auth.service';

export const authGuard: CanActivateFn =
  (route, state) => {

    const authService =
      inject(AuthService);

    const router =
      inject(Router);

    if (authService.isLoggedIn()) {
      return true;
    }

    return router.createUrlTree(
      ['/login'],
      {
        queryParams: {
          returnUrl: state.url
        }
      }
    );
  };

3. Admin Authorization Guard

admin.guard.ts

import { inject } from '@angular/core';
import {
  CanActivateFn,
  Router
} from '@angular/router';

import { AuthService } from './auth.service';

export const adminGuard: CanActivateFn =
  () => {

    const authService =
      inject(AuthService);

    const router =
      inject(Router);

    if (
      authService.isLoggedIn() &&
      authService.role() === 'Admin'
    ) {
      return true;
    }

    return router.createUrlTree(
      ['/unauthorized']
    );
  };

4. Configure Routes

app.routes.ts

import { Routes } from '@angular/router';

import { Login } from './login';
import { Dashboard } from './dashboard';
import { Admin } from './admin';
import { Unauthorized } from './unauthorized';

import { authGuard } from './auth.guard';
import { adminGuard } from './admin.guard';

export const routes: Routes = [

  {
    path: 'login',
    component: Login
  },

  {
    path: 'dashboard',
    component: Dashboard,
    canActivate: [authGuard]
  },

  {
    path: 'admin',
    component: Admin,
    canActivate: [
      authGuard,
      adminGuard
    ]
  },

  {
    path: 'unauthorized',
    component: Unauthorized
  },

  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  }

];

5. Register Router

app.config.ts

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

import {
  provideRouter
} from '@angular/router';

import { routes } from './app.routes';

export const appConfig:
  ApplicationConfig = {

    providers: [
      provideRouter(routes)
    ]

  };

6. Login Component

login.ts

import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

import { AuthService } from './auth.service';

@Component({
  selector: 'app-login',
  standalone: true,
  templateUrl: './login.html'
})
export class Login {

  private authService =
    inject(AuthService);

  private router =
    inject(Router);


  loginAsUser(): void {

    this.authService.login('User');

    this.router.navigate(
      ['/dashboard']
    );
  }


  loginAsAdmin(): void {

    this.authService.login('Admin');

    this.router.navigate(
      ['/admin']
    );
  }
}

login.html

<h2>Login</h2>

<button (click)="loginAsUser()">
  Login as User
</button>

<button (click)="loginAsAdmin()">
  Login as Admin
</button>

7. Dashboard Component

dashboard.ts

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

@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `
    <h2>Dashboard</h2>

    <p>
      Only authenticated users should
      access this page.
    </p>
  `
})
export class Dashboard {
}

8. Admin Component

admin.ts

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

@Component({
  selector: 'app-admin',
  standalone: true,
  template: `
    <h2>Admin Dashboard</h2>

    <p>
      Only administrators should
      access this page.
    </p>
  `
})
export class Admin {
}

9. Unauthorized Component

unauthorized.ts

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

@Component({
  selector: 'app-unauthorized',
  standalone: true,
  template: `
    <h2>403 - Access Denied</h2>

    <p>
      You do not have permission
      to access this page.
    </p>
  `
})
export class Unauthorized {
}

10. How the Authentication Guard Works

When the user enters:

/dashboard

Angular checks:

canActivate: [authGuard]

The flow becomes:

User
  ↓
/dashboard
  ↓
Angular Router
  ↓
authGuard
  ↓
Is authenticated?
   /       \
 Yes        No
  ↓          ↓
Dashboard   /login

The guard prevents normal Angular navigation to the protected route.


11. How Role-Based Route Protection Works

For:

/admin

we configured:

canActivate: [
  authGuard,
  adminGuard
]

The conceptual flow is:

/admin
   ↓
authGuard
   ↓
Logged in?
   ↓
adminGuard
   ↓
Role == Admin?
   /       \
 Yes        No
  ↓          ↓
Admin       /unauthorized

This provides client-side authorization behavior.


12. But Angular Guards Are NOT Real Security

This is the most important point.

Suppose Angular protects:

/admin/users

with:

canActivate: [adminGuard]

but the backend exposes:

DELETE /api/users/10

without authorization.

An attacker doesn't have to use your Angular application.

They could attempt:

Postman
   ↓
DELETE /api/users/10
   ↓
ASP.NET Core

The Angular guard isn't involved at all.

Therefore:

Angular Guard
     ↓
Client-side navigation protection

ASP.NET Core [Authorize]
     ↓
Actual server-side security

13. Secure the ASP.NET Core API

For authenticated users:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class ProfileController : ControllerBase
{
    [HttpGet]
    public IActionResult GetProfile()
    {
        return Ok("Profile information");
    }
}

Now anonymous users cannot access the endpoint successfully.


14. Role-Based Backend Authorization

For admin functionality:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "Admin")]
public class AdminController : ControllerBase
{
    [HttpGet("users")]
    public IActionResult GetUsers()
    {
        return Ok("User list");
    }

    [HttpDelete("users/{id}")]
    public IActionResult DeleteUser(int id)
    {
        return Ok(
            $"User {id} deleted"
        );
    }
}

Now the server itself checks:

Is authenticated?
      ↓
Is role Admin?
      ↓
Execute API

Even if somebody bypasses Angular, ASP.NET Core still enforces authorization.


15. Recommended Architecture

For your Angular + ASP.NET Core application:

                 User
                   |
                   v
              Angular
                   |
            Route Guard
                   |
          +--------+--------+
          |                 |
       Allowed            Blocked
          |                 |
          v                 v
       Component       Login / 403
          |
          v
      HttpClient
          |
          v
   ASP.NET Core API
          |
      Authentication
          |
      Authorization
          |
     Policy / Role
          |
          v
   Business Operation
          |
          v
      SQL Server

The backend authorization check is authoritative.


16. Authentication vs Authorization Guard

These are conceptually different.

Authentication Guard

Checks:

Is the user logged in?

Example:

if (authService.isLoggedIn()) {
  return true;
}

Used for:

/dashboard
/profile
/orders

Authorization Guard

Checks:

Does the authenticated user have permission?

Example:

if (authService.role() === 'Admin') {
  return true;
}

Used for:

/admin
/admin/users
/admin/articles

17. Role-Based vs Policy-Based Authorization

For simple applications, roles can work:

[Authorize(Roles = "Admin")]

But larger applications often benefit from policy-based authorization.

For example:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(
        "CanManageArticles",
        policy =>
        {
            policy.RequireClaim(
                "permission",
                "articles.manage");
        });
});

Then:

[Authorize(
    Policy = "CanManageArticles")]
[HttpDelete("{id}")]
public IActionResult DeleteArticle(
    Guid id)
{
    // Delete article

    return NoContent();
}

This can be more flexible than hard-coding roles everywhere.


18. Handle 401 and 403 Correctly

Your Angular application should distinguish:

401 Unauthorized

from:

403 Forbidden

Despite the HTTP status name, think of 401 as an authentication problem.

401

Not authenticated
or authentication expired

Usually:

401
 ↓
Login page

403

Authenticated
but insufficient permission

Usually:

403
 ↓
Unauthorized / Access Denied page

So:

API Response
     |
     +---- 401 ----> Login
     |
     +---- 403 ----> Access Denied

19. Use an HTTP Error Interceptor

In your real Angular application, you can globally handle these responses.

import {
  HttpErrorResponse,
  HttpInterceptorFn
} from '@angular/common/http';

import { inject } from '@angular/core';
import { Router } from '@angular/router';

import {
  catchError,
  throwError
} from 'rxjs';

export const errorInterceptor:
  HttpInterceptorFn = (req, next) => {

    const router = inject(Router);

    return next(req).pipe(

      catchError(
        (error: HttpErrorResponse) => {

          if (error.status === 401) {

            router.navigate(
              ['/login']
            );

          }
          else if (error.status === 403) {

            router.navigate(
              ['/unauthorized']
            );

          }

          return throwError(
            () => error
          );
        }
      )

    );
  };

Then register it:

provideHttpClient(
  withInterceptors([
    errorInterceptor
  ])
)

The route guard and interceptor have different purposes:

Route Guard
    ↓
Prevent inappropriate navigation

Error Interceptor
    ↓
React to authorization failures
returned by the API

20. What About CanMatch?

For sensitive lazy-loaded routes, CanMatch can also be useful.

Example:

{
  path: 'admin',
  canMatch: [adminGuard],
  loadComponent: () =>
    import('./admin')
      .then(m => m.Admin)
}

CanMatch determines whether a route configuration can be matched.

This can be useful when you don't want an unauthorized user to match a particular route.

But again:

CanMatch is still client-side behavior, not a substitute for ASP.NET Core authorization.


21. Hiding an Admin Button Is Not Security

Suppose you write:

@if (authService.role() === 'Admin') {

  <button>
    Delete User
  </button>

}

That's useful for UI/UX.

But it doesn't secure:

DELETE /api/users/10

The server still needs something such as:

[Authorize(Roles = "Admin")]

Think:

Hide button
     =
UX

Route Guard
     =
Navigation control

[Authorize]
     =
Security

22. Key Points

1. Use Angular route guards

For example:

canActivate: [authGuard]

2. Use authorization guards for roles/permissions

For example:

canActivate: [
  authGuard,
  adminGuard
]

3. Never rely on Angular guards for security

Angular runs in the user's browser and can be bypassed.

4. Secure the ASP.NET Core API

Use:

[Authorize]

or:

[Authorize(Roles = "Admin")]

or preferably policies where they fit:

[Authorize(
    Policy = "CanManageArticles")]

5. Handle 401 and 403 differently

401 → Authentication issue → Login

403 → Authorization issue → Access Denied

6. Don't treat hidden UI elements as security

Hiding buttons or menus improves UX but doesn't protect the underlying API.


23. Interview Questions and Answers

1. How do you secure sensitive routes in Angular?

Answer:
Use Angular route guards such as CanActivate or CanMatch to control client-side navigation. However, the corresponding ASP.NET Core endpoints must also enforce authorization using [Authorize], roles, claims, or policies.


2. Is an Angular route guard enough for security?

No.

Angular runs on the client and can be bypassed. The backend must always enforce authorization independently.


3. What is the difference between authentication and authorization guards?

Authentication checks whether the user is logged in.

Authorization checks whether the authenticated user has the required role or permission.


4. What should happen on 401?

A 401 indicates that valid authentication is missing or no longer accepted.

Angular will commonly redirect the user to:

/login

5. What should happen on 403?

A 403 means the user is authenticated but doesn't have sufficient permission.

Angular can redirect to:

/unauthorized

6. Should we hide unauthorized menu items?

Yes, for a better user experience.

But hiding:

Admin
Delete
Edit

buttons does not provide security. The corresponding APIs must still enforce authorization.


7. CanActivate vs [Authorize]?

CanActivate
    ↓
Angular
    ↓
Client-side navigation


[Authorize]
    ↓
ASP.NET Core
    ↓
Server-side security

[Authorize] is the authoritative security control.


Interview Quick Answer

I secure sensitive routes at both the Angular and ASP.NET Core levels. Angular route guards such as CanActivate or CanMatch control client-side navigation and improve UX, while ASP.NET Core uses [Authorize], roles, claims, or authorization policies to enforce actual security. I also handle 401 by directing unauthenticated users to login and 403 by showing an access-denied page. Angular guards never replace backend authorization.

← Back to Articles