← Back to Articles         
Protect API endpoints and Protect Angular Routes

Protect API endpoints and Protect Angular Routes

Published on 01 Oct 2026     9 min read Angular
Authentication

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:

  1. The client sends a JWT in the Authorization header.
  2. ASP.NET Core validates the JWT.
  3. [Authorize] requires an authenticated user.
  4. 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() before UseAuthorization().
  • 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.
  • CanActivate is commonly used for authenticated routes.
  • CanMatch controls 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 as CanActivate or CanMatch. 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.

← Back to Articles