1. How Do You Protect ASP.NET Core API Endpoints?
What is it?
ASP.NET Core API endpoints are protected using authentication and authorization.
With JWT authentication:
- The client sends a JWT in the
Authorizationheader. - ASP.NET Core validates the JWT.
[Authorize]requires an authenticated user.- Roles, claims, or policies can further restrict access.
Angular
│
│ Authorization: Bearer <JWT>
▼
ASP.NET Core
│
├── Validate JWT
▼
Authentication
│
▼
Authorization
│
▼
[Authorize]
│
▼
Controller / Action
Full Simple ASP.NET Core Program
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var jwtKey = builder.Configuration["Jwt:Key"]!;
var jwtIssuer = builder.Configuration["Jwt:Issuer"]!;
var jwtAudience = builder.Configuration["Jwt:Audience"]!;
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
IssuerSigningKey =
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtKey))
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
appsettings.json
{
"Jwt": {
"Key": "ThisIsMySuperSecretKeyForJwtAuthentication12345",
"Issuer": "MyWebApi",
"Audience": "MyAngularApp"
}
}
For production, keep signing secrets in a secure secret store rather than source-controlled configuration.
Protect a Controller
Use:
[Authorize]
Example:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JwtDemo.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
return Ok(new[]
{
"Laptop",
"Mouse",
"Keyboard"
});
}
}
Now every action inside ProductsController requires authentication.
/api/products
↓
[Authorize]
↓
Valid JWT?
↙ ↘
Yes No
↓ ↓
200 401
Protect Only One Action
You don't have to put [Authorize] on the entire controller.
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
return Ok("Public products");
}
[Authorize]
[HttpPost]
public IActionResult CreateProduct()
{
return Ok("Product created");
}
}
Here:
GET /api/products → Public
POST /api/products → JWT required
Protect by Role
Suppose the JWT contains:
Role = Admin
You can use:
[Authorize(Roles = "Admin")]
Example:
[Authorize(Roles = "Admin")]
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
return Ok("Product deleted");
}
Now:
Valid JWT
↓
Authenticated
↓
Role = Admin?
↙ ↘
Yes No
↓ ↓
Allow 403
Protect by Multiple Roles
[Authorize(Roles = "Admin,Manager")]
This allows a user in either the Admin or Manager role.
Protect Using Authorization Policy
For more complex requirements, policies are preferable.
Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(
"AdminOnly",
policy =>
{
policy.RequireRole("Admin");
});
});
Controller:
[Authorize(Policy = "AdminOnly")]
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
return Ok();
}
Policies can also require claims:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(
"CanDeleteProducts",
policy =>
{
policy.RequireClaim(
"permission",
"products.delete");
});
});
Then:
[Authorize(Policy = "CanDeleteProducts")]
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
return Ok();
}
This is useful when authorization becomes more granular than simple roles.
Allow an Anonymous Endpoint
If a controller is protected:
[Authorize]
public class ProductsController : ControllerBase
but one action should be public, use:
[AllowAnonymous]
Example:
[AllowAnonymous]
[HttpGet]
public IActionResult GetProducts()
{
return Ok();
}
2. How Do You Protect Angular Routes?
What is it?
Angular routes are protected using route guards.
For authentication, a common guard is:
CanActivate
In modern standalone Angular, functional guards are commonly used.
Example requirement:
/dashboard
Logged in
↓
Allow
Not logged in
↓
Redirect → /login
Full Simple Angular Program
We'll create:
auth.service.ts
auth.guard.ts
login.ts
dashboard.ts
app.routes.ts
app.config.ts
auth.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
isLoggedIn(): boolean {
const token =
localStorage.getItem('token');
return !!token;
}
logout(): void {
localStorage.removeItem('token');
}
}
For simplicity, this checks only whether a token exists. A real application may also inspect expiration for UI/navigation purposes, while the backend remains responsible for actual token validation.
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'
]);
};
login.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-login',
standalone: true,
template: `
<h2>Login</h2>
<p>Please login to continue.</p>
`
})
export class Login {
}
dashboard.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-dashboard',
standalone: true,
template: `
<h2>Dashboard</h2>
<p>
Protected dashboard page.
</p>
`
})
export class Dashboard {
}
app.routes.ts
import { Routes } from '@angular/router';
import { Login } from './login';
import { Dashboard } from './dashboard';
import {
authGuard
} from './auth.guard';
export const routes: Routes = [
{
path: 'login',
component: Login
},
{
path: 'dashboard',
component: Dashboard,
canActivate: [
authGuard
]
},
{
path: '',
redirectTo: 'login',
pathMatch: 'full'
}
];
The important part is:
{
path: 'dashboard',
component: Dashboard,
canActivate: [authGuard]
}
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)
]
};
How the Angular Guard Works
Suppose the user enters:
http://localhost:4200/dashboard
Angular checks:
canActivate: [authGuard]
The guard runs:
if (authService.isLoggedIn()) {
return true;
}
If a token exists:
/dashboard
↓
authGuard
↓
Token exists?
↓
YES
↓
return true
↓
Dashboard
If not:
/dashboard
↓
authGuard
↓
Token exists?
↓
NO
↓
UrlTree → /login
↓
Login
Returning a UrlTree is preferable to manually calling navigate() and then returning false.
Protect Angular Route by Role
Suppose you want:
/admin
to be available only to Admin users.
A simple role guard could be:
import { inject } from '@angular/core';
import {
CanActivateFn,
Router
} from '@angular/router';
export const adminGuard: CanActivateFn =
() => {
const router = inject(Router);
const role =
localStorage.getItem('role');
if (role === 'Admin') {
return true;
}
return router.createUrlTree([
'/unauthorized'
]);
};
Route:
{
path: 'admin',
component: AdminDashboard,
canActivate: [
authGuard,
adminGuard
]
}
Conceptually:
/admin
↓
authGuard
↓
Authenticated?
↓
adminGuard
↓
Admin?
↓
Admin Dashboard
However, this Angular role check is only for client-side navigation/UI behavior. The corresponding backend endpoint must still enforce the role:
[Authorize(Roles = "Admin")]
ASP.NET Core Protection vs Angular Protection
This distinction is extremely important.
| Angular | ASP.NET Core |
|---|---|
| Route Guard | [Authorize] |
| Protects client navigation | Protects API/resource |
| Runs in browser | Runs on server |
| Can redirect to login | Can reject API request |
| Useful for UX | Actual security boundary |
| Can be bypassed by client | Cannot be bypassed by changing Angular code |
Why Angular Guard Is Not Real API Security
Suppose you have:
{
path: 'admin',
component: Admin,
canActivate: [adminGuard]
}
The guard prevents normal navigation through Angular.
But someone can ignore Angular completely and send:
DELETE https://api.example.com/api/products/10
Authorization: Bearer <token>
using tools such as an HTTP client.
Therefore:
Angular Guard
↓
Protects navigation
ASP.NET Core [Authorize]
↓
Protects server resources
The backend must always enforce authorization.
Recommended Combined Flow
For example, an Admin page:
User opens /admin
↓
Angular adminGuard
↓
Is user apparently Admin?
↓
YES
↓
Display Admin Page
↓
Call ASP.NET Core
↓
Authorization: Bearer <JWT>
↓
ASP.NET Core validates JWT
↓
[Authorize(Roles = "Admin")]
↓
Role actually authorized?
↓
YES → Return data
NO → 403 Forbidden
This gives both:
Angular
→ Better user experience
ASP.NET Core
→ Actual security
CanActivate vs CanMatch
Another important Angular distinction:
CanActivate
Determines whether an already-matched route can be activated.
{
path: 'dashboard',
component: Dashboard,
canActivate: [authGuard]
}
CanMatch
Determines whether Angular should match that route configuration in the first place.
{
path: 'admin',
canMatch: [adminGuard],
loadComponent: () =>
import('./admin')
.then(m => m.Admin)
}
CanMatch is particularly useful with lazy-loaded or alternative route configurations.
Neither replaces backend authorization.
Key Points
ASP.NET Core
- Configure authentication using
AddAuthentication(). - Configure JWT using
AddJwtBearer(). - Use
UseAuthentication()beforeUseAuthorization(). - Use
[Authorize]to protect endpoints. - Use
[Authorize(Roles = "Admin")]for role-based authorization. - Use policies for more complex authorization.
- Use
[AllowAnonymous]for intentionally public endpoints. - The backend is the actual security boundary.
Angular
- Use route guards to control navigation.
CanActivateis commonly used for authenticated routes.CanMatchcontrols route matching.- Functional guards are appropriate for modern standalone Angular.
- Redirect unauthenticated users to
/login. - Redirect unauthorized users to an appropriate page such as
/unauthorized. - Never depend solely on Angular guards for API security.
Interview Questions and Answers
1. How do you protect ASP.NET Core API endpoints?
Configure authentication and authorization, then apply [Authorize] to protected controllers or actions.
[Authorize]
[HttpGet]
public IActionResult Get()
2. How do you protect an API endpoint by role?
Use:
[Authorize(Roles = "Admin")]
ASP.NET Core checks the authenticated user's role claims.
3. How do you protect an API using a policy?
Define a policy:
options.AddPolicy(
"AdminOnly",
policy => policy.RequireRole("Admin"));
Then:
[Authorize(Policy = "AdminOnly")]
4. How do you make an endpoint public?
Use:
[AllowAnonymous]
when it would otherwise inherit an authorization requirement.
5. How do you protect Angular routes?
Use Angular route guards such as CanActivate or CanMatch.
{
path: 'dashboard',
component: Dashboard,
canActivate: [authGuard]
}
6. What should an authentication guard return?
Common return values include:
true
to allow navigation, or a UrlTree/redirect result to send the user elsewhere.
For example:
return router.createUrlTree(['/login']);
7. Can Angular route guards provide real API security?
No.
Angular executes in the browser and can be bypassed. Route guards are primarily client-side navigation controls.
ASP.NET Core must independently protect API endpoints.
8. What is the difference between [Authorize] and an Angular route guard?
Angular Route Guard
→ Protects client navigation
ASP.NET Core [Authorize]
→ Protects server/API resources
[Authorize] is the security enforcement mechanism for the backend.
9. Should I use both Angular guards and ASP.NET Core [Authorize]?
Yes, they serve different purposes:
Angular Guard
↓
Good navigation / UX
ASP.NET Core [Authorize]
↓
Actual backend security
Interview Quick Answer
ASP.NET Core API endpoints are protected using JWT authentication plus
[Authorize], roles, claims, or authorization policies. Angular routes are protected using route guards such asCanActivateorCanMatch. Angular guards improve client-side navigation and user experience, but ASP.NET Core authorization must always enforce the actual security because client-side guards can be bypassed.