← Back to Articles         
How do you secure an Angular + ASP.NET Core application?

How do you secure an Angular + ASP.NET Core application?

Published on 02 Oct 2026     15 min read Angular
Security

How Do You Secure an Angular + ASP.NET Core Application?

1. What is Security in Angular + ASP.NET Core?

In an Angular + ASP.NET Core application, security means protecting:

  • Authentication — Who is the user?
  • Authorization — What is the user allowed to do?
  • API endpoints
  • Sensitive data
  • User credentials
  • Tokens/cookies
  • Database access
  • Communication between browser and server
  • Application configuration and secrets

The most important architectural principle is:

Angular can improve the user experience around security, but ASP.NET Core must enforce the actual security rules.

For example, an Angular route guard can prevent a normal user from navigating to /admin, but it cannot securely protect the underlying /api/admin endpoint. The ASP.NET Core API must independently authorize the request.


2. Typical Secure Architecture

                    HTTPS
                       |
                       v
+-----------------------------+
|       Angular Browser       |
|                             |
| Login Form                  |
| Route Guards                |
| HTTP Interceptor            |
| Input Validation            |
+-------------+---------------+
              |
              | HTTPS
              | Authorization: Bearer <JWT>
              v
+-----------------------------------------+
|         ASP.NET Core Web API            |
|                                         |
| Authentication                          |
| Authorization                           |
| JWT Validation                          |
| Role / Policy Authorization             |
| CORS                                    |
| Rate Limiting                           |
| Input Validation                        |
| Exception Handling                      |
+--------------------+--------------------+
                     |
                     v
+-----------------------------------------+
|             SQL Server                  |
|                                         |
| Parameterized Queries / EF Core         |
| Least-privilege DB account              |
+-----------------------------------------+

Secrets:
Azure Key Vault / Environment Variables

A production application normally uses multiple security layers, rather than relying on one mechanism such as JWT.


3. Full Simple Sample Program

Consider an Angular application calling:

https://localhost:7001/api/products

We will protect the endpoint using JWT.


ASP.NET Core Web API

appsettings.json

{
  "Jwt": {
    "Issuer": "MyApi",
    "Audience": "MyAngularApp",
    "Key": "ThisIsOnlyADevelopmentExampleSecretKey123456789"
  }
}

Important: this is for demonstration only. A production signing secret should not be committed to appsettings.json or source control.


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"]!;

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(jwtKey))
            };
    });

builder.Services.AddAuthorization();

builder.Services.AddCors(options =>
{
    options.AddPolicy("AngularApp", policy =>
    {
        policy
            .WithOrigins("http://localhost:4200")
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("fixed", limiter =>
    {
        limiter.PermitLimit = 100;
        limiter.Window = TimeSpan.FromMinutes(1);
    });
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseCors("AngularApp");

app.UseRateLimiter();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

AuthController.cs

For simplicity, the username and password are hard-coded.

In a real application, the user would normally be retrieved from a database and the password verified using a secure password hashing mechanism or an identity provider.

using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("login")]
    public IActionResult Login(LoginRequest request)
    {
        // Demo only
        if (request.Username != "admin" ||
            request.Password != "Admin@123")
        {
            return Unauthorized();
        }

        var claims = new[]
        {
            new Claim(ClaimTypes.Name, request.Username),
            new Claim(ClaimTypes.Role, "Admin")
        };

        var key =
            new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(
                    _configuration["Jwt:Key"]!));

        var credentials =
            new SigningCredentials(
                key,
                SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(15),
            signingCredentials: credentials);

        var jwt =
            new JwtSecurityTokenHandler()
                .WriteToken(token);

        return Ok(new
        {
            accessToken = jwt
        });
    }
}

public record LoginRequest(
    string Username,
    string Password);

ProductsController.cs

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

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetProducts()
    {
        var products = new[]
        {
            new
            {
                Id = 1,
                Name = "Laptop",
                Price = 50000
            },
            new
            {
                Id = 2,
                Name = "Mouse",
                Price = 1000
            }
        };

        return Ok(products);
    }

    [HttpDelete("{id}")]
    [Authorize(Roles = "Admin")]
    public IActionResult DeleteProduct(int id)
    {
        return Ok(new
        {
            Message = $"Product {id} deleted"
        });
    }
}

Now:

GET /api/products

requires an authenticated user.

And:

DELETE /api/products/1

requires:

Role = Admin

Angular Application

The Angular side needs to:

  1. Login
  2. Receive the access token
  3. Attach it to API requests
  4. Handle 401/403
  5. Use guards for navigation UX

auth.service.ts

import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs';

interface LoginResponse {
  accessToken: string;
}

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

  private http = inject(HttpClient);

  private apiUrl =
    'https://localhost:7001/api/auth';

  login(username: string, password: string) {

    return this.http
      .post<LoginResponse>(
        `${this.apiUrl}/login`,
        {
          username,
          password
        }
      )
      .pipe(
        tap(response => {

          // Simplified demo storage.
          sessionStorage.setItem(
            'access_token',
            response.accessToken
          );

        })
      );
  }

  getToken(): string | null {

    return sessionStorage.getItem(
      'access_token'
    );
  }

  isLoggedIn(): boolean {

    return !!this.getToken();
  }

  logout(): void {

    sessionStorage.removeItem(
      'access_token'
    );
  }
}

For this simple demonstration we're using sessionStorage. Token storage deserves its own security decision; for many production browser applications, an architecture using secure HttpOnly cookies/BFF or carefully designed short-lived access tokens can reduce exposure to token theft.


auth.interceptor.ts

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

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

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

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

    const authService =
      inject(AuthService);

    const token =
      authService.getToken();

    if (!token) {
      return next(req);
    }

    const authRequest =
      req.clone({
        setHeaders: {
          Authorization:
            `Bearer ${token}`
        }
      });

    return next(authRequest);
};

Every outgoing API request can now contain:

Authorization: Bearer eyJhbGciOi...

error.interceptor.ts

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
          );
        }
      )
    );
};

This gives us centralized handling for common authentication/authorization failures.


auth.guard.ts

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

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

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

export const authGuard:
  CanActivateFn = () => {

    const authService =
      inject(AuthService);

    const router =
      inject(Router);

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

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

app.routes.ts

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

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

import { Login }
  from './login';

import { Products }
  from './products';

import { Unauthorized }
  from './unauthorized';

export const routes: Routes = [

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

  {
    path: 'products',
    component: Products,
    canActivate: [authGuard]
  },

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

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

];

app.config.ts

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

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

import {
  provideHttpClient,
  withInterceptors
} from '@angular/common/http';

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

import { authInterceptor }
  from './auth.interceptor';

import { errorInterceptor }
  from './error.interceptor';

export const appConfig:
  ApplicationConfig = {

    providers: [

      provideRouter(routes),

      provideHttpClient(
        withInterceptors([
          authInterceptor,
          errorInterceptor
        ])
      )

    ]
};

This is the modern standalone Angular configuration.


4. How the Security Flow Works

Suppose the user opens:

/products

The flow is:

User
 |
 v
Angular Login
 |
 | username/password
 v
POST /api/auth/login
 |
 v
ASP.NET Core
 |
 | Validate credentials
 | Generate JWT
 |
 v
Angular
 |
 | Store token
 |
 v
GET /api/products
 |
 | Auth Interceptor
 | adds:
 |
 | Authorization: Bearer <token>
 |
 v
ASP.NET Core
 |
 | Authentication Middleware
 | validates JWT
 |
 v
Authorization
 |
 | [Authorize]
 |
 v
ProductsController
 |
 v
200 OK

If the token is invalid or expired:

API
 |
 v
401 Unauthorized
 |
 v
Angular Error Interceptor
 |
 v
/login

If the user is authenticated but doesn't have the required permission:

API
 |
 v
403 Forbidden
 |
 v
Angular Error Interceptor
 |
 v
/unauthorized

5. Authentication vs Authorization

These are frequently confused in interviews.

Authentication

Authentication answers:

Who are you?

Example:

[Authorize]

The API requires an authenticated user.


Authorization

Authorization answers:

What are you allowed to do?

Example:

[Authorize(Roles = "Admin")]

Only users with the Admin role can access the endpoint.

For more complex applications, policy-based authorization is generally preferable to spreading role checks everywhere.


6. Angular Route Guards Are NOT Security

Suppose Angular has:

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

The guard can prevent normal navigation to:

/admin

But an attacker doesn't need to use your Angular UI.

They can call:

DELETE https://api.example.com/api/products/10

directly using another HTTP client.

Therefore this:

canActivate: [adminGuard]

is not sufficient.

The API must enforce authorization:

[Authorize(Roles = "Admin")]
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
    ...
}

Think of it this way:

Angular Guard
     |
     | UX/navigation protection
     v
Angular Page


ASP.NET Core [Authorize]
     |
     | Actual security boundary
     v
Protected resource

7. Use HTTPS

Production communication should use:

HTTPS

rather than:

HTTP

HTTPS protects data in transit using TLS.

For example:

Angular
   |
   | HTTPS
   | encrypted
   v
ASP.NET Core API

ASP.NET Core commonly includes:

app.UseHttpsRedirection();

Production deployments should also have appropriate TLS and HSTS configuration.


8. Configure CORS Correctly

Avoid unnecessarily broad production policies.

For example, don't default to:

policy.AllowAnyOrigin()
      .AllowAnyHeader()
      .AllowAnyMethod();

Instead specify the Angular origin:

policy
    .WithOrigins(
        "https://www.example.com")
    .AllowAnyHeader()
    .AllowAnyMethod();

Also remember:

CORS is a browser cross-origin policy, not an authentication or authorization mechanism.

It does not replace [Authorize].


9. Never Store Secrets in Angular

This is extremely important.

Don't put secrets such as these in Angular:

export const environment = {

  databasePassword: 'password123',

  jwtSigningKey:
    'MySecretSigningKey',

  stripeSecret:
    'secret-value'
};

Angular runs in the user's browser.

Anything delivered to the browser should effectively be considered accessible to the user.

Instead:

Angular
   |
   | Normal API request
   v
ASP.NET Core
   |
   | Reads secret
   v
Environment Variable
       or
Azure Key Vault

Sensitive secrets belong on the trusted server side.


10. Prevent SQL Injection

Never construct SQL using raw user input like:

var sql =
    "SELECT * FROM Users WHERE Name = '"
    + username +
    "'";

Use parameterized queries.

EF Core normally parameterizes values in LINQ queries:

var user =
    await db.Users
        .FirstOrDefaultAsync(
            x => x.Username == username);

For raw SQL, use parameterized APIs rather than string concatenation.


11. Prevent XSS

Cross-Site Scripting occurs when attacker-controlled content is executed as JavaScript in another user's browser.

Angular automatically sanitizes/escapes many values in normal template usage.

For example:

<div>
  {{ comment }}
</div>

is much safer than deliberately bypassing Angular's sanitization.

Be particularly careful with:

[innerHTML]

and especially with APIs such as DomSanitizer.bypassSecurityTrustHtml().

Never treat arbitrary user HTML as trusted simply to make it render.


12. CSRF Protection

CSRF is particularly important when authentication credentials are automatically sent by the browser, such as cookies.

For cookie-based authentication, use appropriate anti-forgery protection, cookie settings, and same-site strategy.

For example, cookies containing authentication credentials should generally be configured with security attributes such as:

Secure
HttpOnly
SameSite

The exact SameSite value depends on whether your Angular application and API are same-site or cross-site.


13. Password Security

Never store:

Password = "Admin@123"

directly in a database.

Store a secure password hash using a proven password-hashing implementation.

Common ASP.NET Core applications use:

ASP.NET Core Identity

or an external identity provider rather than implementing password storage themselves.

Conceptually:

Password
   |
   v
Password Hashing
   |
   v
Hash
   |
   v
Database

14. Rate Limiting

Rate limiting helps protect endpoints from excessive traffic and some abuse patterns.

For example:

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter(
        "fixed",
        limiter =>
        {
            limiter.PermitLimit = 100;

            limiter.Window =
                TimeSpan.FromMinutes(1);
        });
});

It is particularly useful around endpoints such as:

/login
/password-reset
/otp
/search
/public APIs

Authentication endpoints often need stricter, identity/IP-aware anti-abuse controls than a generic global request limit.


15. Validate Input on Both Sides

Angular validation:

Validators.required
Validators.email
Validators.minLength(8)

is useful for UX.

But users can bypass Angular entirely.

Therefore ASP.NET Core must validate the request independently.

For example:

public class RegisterRequest
{
    [Required]
    [EmailAddress]
    public string Email { get; set; } = "";

    [Required]
    [MinLength(8)]
    public string Password { get; set; } = "";
}

The rule is:

Angular validation
      +
Server validation

But only the server-side validation is part of the security boundary.


16. Don't Expose Internal Exception Details

Don't send detailed production exceptions such as:

SqlException:
Login failed for user...
Server=...
Database=...
StackTrace...

to the browser.

Instead return a controlled error format such as ASP.NET Core ProblemDetails:

{
  "type": "https://example.com/errors/server-error",
  "title": "An unexpected error occurred.",
  "status": 500
}

Keep detailed diagnostic information in protected server logs.


17. Access Token + Refresh Token

A stronger JWT design commonly uses:

Access Token
+
Refresh Token

For example:

Access Token
Lifetime: short
Purpose: API access

Refresh Token
Lifetime: longer
Purpose: obtain a new access token

Flow:

Login
  |
  v
Access Token + Refresh Token
  |
  v
Angular calls API
  |
  v
Access token expires
  |
  v
Refresh endpoint
  |
  v
New access token

Refresh tokens should be protected carefully and support rotation/revocation where appropriate.


18. Security Responsibilities

A useful way to remember the architecture is:

Security concern Angular ASP.NET Core
Login UI Yes Validates credentials
Route guard Yes No
JWT attachment Yes Validates JWT
Authentication enforcement No Yes
Authorization enforcement No Yes
Form validation Yes Yes
CORS No Yes
Rate limiting No Yes
Database protection No Yes
Secret storage Never Yes
XSS mitigation Yes Also validate/encode appropriately
CSRF protection Depends on auth model Yes
Logging/auditing Limited Yes

19. Production Security Checklist

For an Angular + ASP.NET Core application, remember these core controls:

  1. HTTPS/TLS everywhere
  2. Strong authentication
  3. Server-side authorization
  4. Short-lived access tokens or secure cookie-based sessions
  5. Secure refresh/session lifecycle
  6. Angular HTTP interceptor
  7. Angular route guards for UX only
  8. Correct CORS allowlist
  9. Server-side request validation
  10. XSS prevention
  11. CSRF protection when applicable
  12. Parameterized database access
  13. Rate limiting / anti-abuse controls
  14. Secrets outside Angular/source control
  15. Secure password hashing / established identity provider
  16. Safe error responses
  17. Security logging and auditing
  18. Least-privilege access to databases and infrastructure
  19. Security headers and secure cookie configuration
  20. Keep Angular, .NET, npm and NuGet dependencies patched

20. Key Points

Point 1 — Backend is the security boundary

Never depend on Angular alone.

Angular = client
ASP.NET Core = trusted security enforcement

Point 2 — Guards aren't authorization

canActivate: [authGuard]

improves navigation behavior.

Actual protection belongs in ASP.NET Core:

[Authorize]

or:

[Authorize(Policy = "CanDeleteProducts")]

Point 3 — Never put secrets in Angular

Anything shipped to Angular can be inspected by the user.

Secret → Backend only

Point 4 — 401 and 403 are different

401 Unauthorized
    ↓
Not authenticated / authentication failed

403 Forbidden
    ↓
Authenticated but not permitted

Despite the HTTP name 401 Unauthorized, it represents an authentication problem.


Point 5 — Use defense in depth

Don't think:

JWT = application is secure

Think:

HTTPS
  +
Authentication
  +
Authorization
  +
Validation
  +
XSS/CSRF defenses
  +
CORS
  +
Rate limiting
  +
Secure secrets
  +
Database security
  +
Logging
  +
Dependency patching

Interview Questions and Answers

1. How do you secure an Angular + ASP.NET Core application?

Use HTTPS, strong authentication, server-side authorization, secure token/cookie handling, input validation, restricted CORS, XSS/CSRF protection, rate limiting, safe database access, secure secret storage, logging, and least-privilege access.

The ASP.NET Core backend must enforce security because Angular runs on the client and can be bypassed.


2. Is an Angular route guard sufficient for security?

No.

A route guard only controls Angular navigation. Users can bypass Angular and directly call the API, so ASP.NET Core must enforce authorization using [Authorize], roles, policies, or resource-based authorization.


3. Where should JWT validation happen?

In ASP.NET Core.

.AddAuthentication()
.AddJwtBearer(...)

Angular may carry and attach the token, but the API validates its signature, issuer, audience, lifetime, and claims.


4. Where should secrets be stored?

Server-side, using mechanisms such as:

Environment variables
Azure Key Vault
Secret managers
Protected configuration

Never store database passwords, JWT signing secrets, or private API credentials in Angular.


5. What is the difference between 401 and 403?

401 means authentication is missing or failed.

403 means the user is authenticated but doesn't have permission to perform the requested operation.


6. Is CORS a security/authentication mechanism?

No.

CORS controls which browser origins are permitted to make cross-origin requests. It does not replace authentication or authorization.


7. Should validation be done in Angular or ASP.NET Core?

Both, but for different reasons.

Angular validation provides immediate user feedback. ASP.NET Core validation is mandatory because client-side validation can be bypassed.


8. How do you prevent SQL injection?

Use EF Core parameterized LINQ queries or explicit parameterized SQL. Never concatenate untrusted input into SQL statements.


9. How do you prevent XSS in Angular?

Use Angular's normal template binding and sanitization, avoid unsafe DOM manipulation, don't blindly render untrusted HTML, and avoid bypassing Angular sanitization unless the content is genuinely trusted.


10. Where should authorization be implemented?

Authorization must be enforced in ASP.NET Core.

For example:

[Authorize(Roles = "Admin")]

or preferably for more complex requirements:

[Authorize(Policy = "CanDeleteProducts")]

Angular can mirror those permissions to hide buttons or protect routes, but that is UX rather than the security boundary.


Interview Quick Answer

How do you secure Angular + ASP.NET Core?

Answer:
I secure the application using HTTPS, authentication with JWT or secure cookies, server-side role/policy authorization, Angular interceptors and route guards, strict CORS configuration, server-side validation, XSS and CSRF protection, rate limiting, parameterized database access, secure secret storage such as Azure Key Vault, safe exception handling, logging, and least-privilege access. Angular guards improve UX, but the ASP.NET Core API always performs the real authentication and authorization checks.

← Back to Articles