For an Angular + ASP.NET Core Web API application, protect the Admin area at both layers:
Angular route guard
+
ASP.NET Core authorization
The Angular guard improves navigation/UX, but the ASP.NET Core API must enforce the actual security boundary.
1. How would you protect an Admin route?
What is an Admin route guard?
Suppose Angular has:
/login
/products
/admin
Only users with the Admin role should navigate to:
/admin
In modern standalone Angular, we can use a functional CanActivateFn guard.
JWT example
Suppose ASP.NET Core issues a JWT containing:
{
"sub": "123",
"name": "Syed",
"role": "Admin"
}
Angular receives the JWT after login.
Auth Service
auth.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
getToken(): string | null {
return localStorage.getItem('access_token');
}
isLoggedIn(): boolean {
return !!this.getToken();
}
hasRole(role: string): boolean {
const token = this.getToken();
if (!token) {
return false;
}
const payload = JSON.parse(
atob(token.split('.')[1])
);
return payload.role === role;
}
}
For a simple demo, this reads the JWT payload.
Decoding a JWT on the client does not prove that it is trustworthy. Angular uses the claim only to control UI/navigation. ASP.NET Core must validate the JWT signature and authorization claims.
Create Admin Guard
admin.guard.ts
import { inject } from '@angular/core';
import {
CanActivateFn,
Router
} from '@angular/router';
import { AuthService } from './auth.service';
export const adminGuard: CanActivateFn =
(route, state) => {
const authService = inject(AuthService);
const router = inject(Router);
if (!authService.isLoggedIn()) {
return router.createUrlTree([
'/login'
]);
}
if (!authService.hasRole('Admin')) {
return router.createUrlTree([
'/forbidden'
]);
}
return true;
};
Apply the Guard
app.routes.ts
import { Routes } from '@angular/router';
import { adminGuard } from './admin.guard';
export const routes: Routes = [
{
path: 'admin',
canActivate: [
adminGuard
],
loadComponent: () =>
import('./admin/admin')
.then(m => m.Admin)
}
];
Now:
User visits /admin
↓
adminGuard
↓
Is logged in?
/ \
No Yes
↓ ↓
Login Has Admin role?
/ \
No Yes
↓ ↓
Forbidden Admin
Protect an Entire Admin Section
Suppose your routes are:
/admin
/admin/users
/admin/articles
/admin/settings
Instead of adding the guard repeatedly, protect the parent route.
export const routes: Routes = [
{
path: 'admin',
canActivate: [
adminGuard
],
children: [
{
path: '',
loadComponent: () =>
import('./admin/dashboard')
.then(m => m.Dashboard)
},
{
path: 'users',
loadComponent: () =>
import('./admin/users')
.then(m => m.Users)
},
{
path: 'articles',
loadComponent: () =>
import('./admin/articles')
.then(m => m.Articles)
}
]
}
];
You can also use canActivateChild when you specifically want a guard applied to child-route activation.
Important Security Point
An Angular route guard is not real API security.
A malicious user doesn't have to use your Angular application.
They can call:
DELETE /api/admin/users/10
using Postman, curl, JavaScript, or another HTTP client.
Therefore:
Angular Guard
↓
Protects client navigation
ASP.NET Core Authorization
↓
Protects server resources/data
That brings us to the second question.
2. How would you protect the corresponding Admin API endpoint?
ASP.NET Core should validate the JWT and then enforce an authorization rule such as the Admin role.
JWT Authentication
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services
.AddAuthentication(
JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer =
builder.Configuration["Jwt:Issuer"],
ValidAudience =
builder.Configuration["Jwt:Audience"],
IssuerSigningKey =
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
builder.Configuration[
"Jwt:Key"]!))
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
The order is important:
app.UseAuthentication();
app.UseAuthorization();
Authentication determines:
Who is this user?
Authorization determines:
Is this authenticated user allowed to perform this operation?
Protect Admin Controller
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/admin")]
[Authorize(Roles = "Admin")]
public class AdminController : ControllerBase
{
[HttpGet("dashboard")]
public IActionResult Dashboard()
{
return Ok(new
{
Message = "Welcome Admin"
});
}
}
The important part is:
[Authorize(Roles = "Admin")]
Now the API requires an authenticated user with the Admin role.
JWT Must Contain the Role Claim
When creating the JWT:
var claims = new List<Claim>
{
new Claim(
ClaimTypes.NameIdentifier,
user.Id.ToString()),
new Claim(
ClaimTypes.Name,
user.UserName),
new Claim(
ClaimTypes.Role,
"Admin")
};
Then create the token from those claims.
Conceptually:
JWT
{
UserId: 123,
Name: "Syed",
Role: "Admin"
}
ASP.NET Core validates the token and then:
[Authorize(Roles = "Admin")]
checks the role claim.
Protect Only Certain Methods
You don't necessarily have to protect the entire controller.
For example:
[ApiController]
[Route("api/articles")]
public class ArticlesController : ControllerBase
{
[HttpGet]
[AllowAnonymous]
public IActionResult GetArticles()
{
return Ok();
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult CreateArticle()
{
return Ok();
}
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")]
public IActionResult DeleteArticle(Guid id)
{
return Ok();
}
}
This is useful for something like a portfolio application:
GET /api/articles
↓
Public
↓
Anyone can read
POST /api/articles
↓
Admin only
↓
[Authorize(Roles = "Admin")]
DELETE /api/articles/{id}
↓
Admin only
Angular Calls the Protected Endpoint
Angular:
deleteArticle(id: string) {
return this.http.delete(
`https://localhost:7001/api/articles/${id}`
);
}
Your JWT interceptor automatically adds:
Authorization: Bearer eyJ...
ASP.NET Core then performs:
Request
↓
JWT Authentication
↓
Is token valid?
│
├── No → 401
│
└── Yes
↓
Authorization
↓
Does user have Admin role?
│
├── No → 403
│
└── Yes
↓
Controller Action
401 vs 403
This is an important interview distinction.
| Situation | Result |
|---|---|
| No authentication credentials | 401 Unauthorized |
| Invalid/expired access token | 401 Unauthorized |
| Valid authenticated user but insufficient permission | 403 Forbidden |
| Valid Admin user | Request proceeds |
For example:
No JWT
↓
401
Valid JWT
Role = User
↓
403
Valid JWT
Role = Admin
↓
200
Role-Based vs Policy-Based Authorization
For a simple Admin requirement:
[Authorize(Roles = "Admin")]
is perfectly reasonable.
For more complex enterprise rules, policy-based authorization is often preferable.
For example:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(
"AdminOnly",
policy =>
{
policy.RequireRole("Admin");
});
});
Then:
[Authorize(Policy = "AdminOnly")]
[HttpDelete("{id}")]
public IActionResult Delete(Guid id)
{
return Ok();
}
A policy can later evolve beyond a simple role check:
options.AddPolicy(
"CanDeleteArticle",
policy =>
{
policy.RequireClaim(
"permission",
"articles.delete");
});
Controller:
[Authorize(Policy = "CanDeleteArticle")]
This is useful when permissions become more granular than Admin versus User.
Complete Architecture
For an Angular + ASP.NET Core application:
LOGIN
│
▼
ASP.NET Core API
│
Issues JWT
│
▼
Angular
│
Stores auth state
│
┌───────────┴───────────┐
│ │
▼ ▼
Angular Router HttpClient
│ │
Admin Guard JWT Interceptor
│ │
Check Admin role Attach Bearer JWT
│ │
▼ ▼
/admin allowed ASP.NET Core Web API
│
Authenticate JWT
│
▼
[Authorize(Roles =
"Admin")]
│
┌────────────┴────────────┐
│ │
Allowed Denied
│ │
200 401 / 403
Key Points
- Protect Angular Admin routes using a route guard such as
CanActivateFn. - The Angular guard is primarily for client-side navigation and UX.
- Never rely on an Angular guard to secure backend data.
- Protect ASP.NET Core endpoints independently with
[Authorize]. - Use
[Authorize(Roles = "Admin")]for straightforward role-based authorization. - The JWT must contain the appropriate role/permission claims.
- ASP.NET Core must validate the JWT; Angular decoding the JWT is not a security check.
401means authentication is missing/invalid;403means the authenticated user lacks permission.- For complex applications, consider policy/permission-based authorization instead of hard-coding roles everywhere.
Interview Answers
Q1. How would you protect an Admin route in Angular?
I would create an Angular route guard, such as a functional CanActivateFn, that checks the user's authentication state and Admin role. If authorized it returns true; otherwise it returns a UrlTree to the login or forbidden page.
Q2. How would you protect the corresponding Admin API endpoint?
I would protect the ASP.NET Core endpoint using server-side authorization, for example:
[Authorize(Roles = "Admin")]
ASP.NET Core validates the JWT and its role claim before executing the endpoint. The Angular guard is only a client-side navigation control; the API authorization is the actual security enforcement.