← Back to Articles         
JWT Authentication

JWT Authentication

Published on 01 Oct 2026     13 min read Angular
Authentication

JWT Authentication Between Angular and ASP.NET Core Web API

1. What is JWT Authentication?

JWT (JSON Web Token) authentication is a token-based authentication mechanism commonly used between an Angular SPA and an ASP.NET Core Web API.

The basic idea is:

Angular sends username/password → Web API validates them → API creates a JWT → Angular sends that JWT with later requests → Web API validates the JWT before allowing access.

JWT stands for JSON Web Token.

A JWT typically contains:

  • User identifier
  • Username/email
  • Roles or other claims
  • Expiration time
  • Issuer
  • Audience
  • Digital signature

A JWT has three parts:

Header.Payload.Signature

Example:

xxxxx.yyyyy.zzzzz

2. Overall Authentication Flow

                LOGIN
Angular                          ASP.NET Core Web API
   |                                      |
   | POST /api/auth/login                 |
   | username + password ---------------->|
   |                                      |
   |                         Validate credentials
   |                                      |
   |                         Generate JWT
   |                                      |
   |<-------------------------- JWT Token |
   |                                      |
   | Store/use token                      |
   |                                      |
   |                                      |
   | GET /api/products                    |
   | Authorization: Bearer JWT ---------->|
   |                                      |
   |                           Validate JWT
   |                           Validate signature
   |                           Validate expiry
   |                           Validate issuer
   |                           Validate audience
   |                                      |
   |<------------------------- 200 + Data |

The important point is that the password is sent only during login. After successful login, the JWT is used for authenticated API requests.


3. Full Simple Sample Program

We'll create:

Angular
   |
   | POST /api/auth/login
   ↓
ASP.NET Core Web API
   |
   | returns JWT
   ↓
Angular
   |
   | Authorization: Bearer <token>
   ↓
GET /api/products
   |
   ↓
[Authorize]

Part 1 — ASP.NET Core Web API

Install Package

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

appsettings.json

{
  "Jwt": {
    "Key": "ThisIsMySuperSecretKeyForJwtAuthentication12345",
    "Issuer": "MyWebApi",
    "Audience": "MyAngularApp"
  },

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },

  "AllowedHosts": "*"
}

In a real application, don't keep production signing secrets directly in appsettings.json. Use a secure secret store or environment-specific secret mechanism.


Models/LoginRequest.cs

namespace JwtDemo.Models;

public class LoginRequest
{
    public string Username { get; set; } = string.Empty;

    public string Password { get; set; } = string.Empty;
}

Models/LoginResponse.cs

namespace JwtDemo.Models;

public class LoginResponse
{
    public string Token { get; set; } = string.Empty;
}

Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();


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


// -----------------------------------------
// JWT Authentication
// -----------------------------------------
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.UseCors("AngularApp");

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

Notice the order:

app.UseAuthentication();
app.UseAuthorization();

Authentication identifies the user first; authorization then determines whether that user can access the endpoint.


Controllers/AuthController.cs

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

namespace JwtDemo.Controllers;

[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.
        // Normally validate against database
        // and verify a securely hashed password.

        if (request.Username != "admin" ||
            request.Password != "admin123")
        {
            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(30),

                signingCredentials:
                    credentials);


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


        return Ok(new LoginResponse
        {
            Token = jwt
        });
    }
}

Controllers/ProductsController.cs

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

namespace JwtDemo.Controllers;

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

Because the controller has:

[Authorize]

the API requires a valid JWT.


Part 2 — Angular

Assume a modern standalone Angular application.

app.config.ts

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

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

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

export const appConfig: ApplicationConfig = {

  providers: [

    provideHttpClient(
      withInterceptors([
        authInterceptor
      ])
    )

  ]
};

auth.service.ts

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

interface LoginResponse {
  token: string;
}

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

  private http = inject(HttpClient);

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


  login(
    username: string,
    password: string
  ): Observable<LoginResponse> {

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

          localStorage.setItem(
            'token',
            response.token
          );

        })
      );
  }


  getToken(): string | null {

    return localStorage.getItem('token');

  }


  logout(): void {

    localStorage.removeItem('token');

  }
}

For simplicity this demo uses localStorage. In production, token storage is a security design decision: localStorage is accessible to JavaScript and therefore exposed if the application has an XSS vulnerability. Cookie-based approaches can reduce token exposure to JavaScript when configured correctly.


auth.interceptor.ts

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


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

    const token =
      localStorage.getItem('token');


    if (token) {

      const clonedRequest =
        req.clone({

          setHeaders: {

            Authorization:
              `Bearer ${token}`

          }

        });


      return next(clonedRequest);
    }


    return next(req);
};

The interceptor automatically adds:

Authorization: Bearer eyJhbGciOi...

to API requests.


product.service.ts

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

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

  private http = inject(HttpClient);

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


  getProducts() {

    return this.http.get(this.apiUrl);

  }
}

Notice that ProductService does not manually add the JWT.

We simply write:

this.http.get(this.apiUrl);

The interceptor handles it.


login.ts

import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AuthService } from './auth.service';

@Component({
  selector: 'app-login',

  standalone: true,

  imports: [
    FormsModule
  ],

  template: `

    <h2>Login</h2>

    <input
      [(ngModel)]="username"
      placeholder="Username">

    <br><br>

    <input
      [(ngModel)]="password"
      type="password"
      placeholder="Password">

    <br><br>

    <button (click)="login()">
      Login
    </button>

  `
})
export class Login {

  private authService =
    inject(AuthService);


  username = '';

  password = '';


  login() {

    this.authService
      .login(
        this.username,
        this.password
      )
      .subscribe({

        next: response => {

          console.log(
            'Login successful',
            response
          );

        },

        error: error => {

          console.log(
            'Login failed',
            error
          );

        }

      });

  }
}

products.ts

import { Component, inject } from '@angular/core';
import { ProductService } from './product.service';

@Component({
  selector: 'app-products',

  standalone: true,

  template: `

    <h2>Products</h2>

    <button (click)="loadProducts()">
      Load Products
    </button>

    <pre>
      {{ products | json }}
    </pre>

  `
})
export class Products {

  private productService =
    inject(ProductService);


  products: any;


  loadProducts() {

    this.productService
      .getProducts()
      .subscribe({

        next: data => {

          this.products = data;

        },

        error: error => {

          console.log(error);

        }

      });

  }
}

For | json, import JsonPipe into the standalone component if you use this exact template:

import { JsonPipe } from '@angular/common';

and:

imports: [JsonPipe]

4. How the Complete Flow Works

Let's understand the program step by step.

Step 1 — User enters credentials

Angular collects:

Username: admin
Password: admin123

Angular calls:

POST /api/auth/login

with:

{
  "username": "admin",
  "password": "admin123"
}

Step 2 — ASP.NET Core validates credentials

The demo checks:

if (request.Username != "admin" ||
    request.Password != "admin123")
{
    return Unauthorized();
}

In a real application this would normally involve:

Angular
   ↓
AuthController
   ↓
User Service
   ↓
Database
   ↓
Password hash verification

You should never store plaintext passwords in a production database.


Step 3 — Web API creates claims

The API creates claims such as:

new Claim(
    ClaimTypes.Name,
    request.Username)

new Claim(
    ClaimTypes.Role,
    "Admin")

Claims are information about the authenticated identity.

For example:

Name = admin
Role = Admin
UserId = 1001

Step 4 — Web API creates JWT

The API generates the token:

var token =
    new JwtSecurityToken(
        issuer: "MyWebApi",
        audience: "MyAngularApp",
        claims: claims,
        expires: DateTime.UtcNow.AddMinutes(30),
        signingCredentials: credentials
    );

The API signs it using the configured signing key.

Conceptually:

Header
+
Payload
+
Signing Key
     ↓
Signature

Result:

xxxxx.yyyyy.zzzzz

5. API Returns JWT to Angular

The API returns:

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Angular receives it:

tap(response => {

  localStorage.setItem(
    'token',
    response.token
  );

})

So the browser now has the access token available to the Angular application.


6. Angular Calls a Protected API

Suppose Angular executes:

this.http.get(
  'https://localhost:7001/api/products'
);

The products endpoint contains:

[Authorize]

Therefore, it requires authentication.


7. Interceptor Adds JWT

Instead of adding the token in every service:

headers: {
  Authorization: `Bearer ${token}`
}

we use an interceptor.

The interceptor automatically changes the request from:

GET /api/products

to:

GET /api/products
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

This is one of the main reasons authentication interceptors are useful.


8. ASP.NET Core Extracts the JWT

The JWT Bearer authentication handler examines:

Authorization: Bearer <JWT>

The configuration comes from:

.AddJwtBearer(options =>
{
    options.TokenValidationParameters =
        new TokenValidationParameters
        {
            ...
        };
});

ASP.NET Core validates the token before the protected controller action executes.


9. What Does ASP.NET Core Validate?

The API can validate several important properties.

Signature

ValidateIssuerSigningKey = true

Checks that the token was signed using a trusted signing key.

Expiration

ValidateLifetime = true

If the token is expired:

Token expired
     ↓
Authentication fails
     ↓
401 Unauthorized

Issuer

ValidateIssuer = true

Checks who issued the token.

Example:

MyWebApi

Audience

ValidateAudience = true

Checks that the token is intended for the configured audience.

Example:

MyAngularApp

10. [Authorize] Protects the Endpoint

Consider:

[Authorize]
[HttpGet]
public IActionResult GetProducts()
{
    ...
}

Valid JWT:

JWT
 ↓
Authentication succeeds
 ↓
[Authorize] succeeds
 ↓
Controller executes
 ↓
200 OK

Missing, invalid, or expired JWT:

No valid authentication
 ↓
[Authorize] fails
 ↓
401 Unauthorized

11. Authentication vs Authorization

These are closely related but different.

Authentication answers:

Who are you?

Example:

User = admin

Authorization answers:

What are you allowed to do?

Example:

Role = Admin

You could protect an endpoint with:

[Authorize(Roles = "Admin")]

Then only authenticated users with the Admin role claim can access it.


12. JWT Authentication Architecture

┌──────────────────────┐
│       Angular        │
│                      │
│ Login Component      │
│ Auth Service         │
│ HTTP Interceptor     │
└──────────┬───────────┘
           │
           │ Username + Password
           ▼
┌──────────────────────────────┐
│    ASP.NET Core Web API      │
│                              │
│ AuthController               │
│      ↓                       │
│ Validate User                │
│      ↓                       │
│ Generate JWT                 │
└─────────────┬────────────────┘
              │
              │ JWT
              ▼
┌──────────────────────┐
│       Angular        │
│                      │
│ Store/use JWT        │
└──────────┬───────────┘
           │
           │ API Request
           │
           │ Authorization:
           │ Bearer <JWT>
           ▼
┌──────────────────────────────┐
│    ASP.NET Core Web API      │
│                              │
│ JWT Bearer Middleware        │
│       ↓                      │
│ Validate JWT                 │
│       ↓                      │
│ [Authorize]                  │
│       ↓                      │
│ ProductsController           │
└──────────────────────────────┘

13. What Happens When the Token Expires?

Suppose the token is created with:

expires:
    DateTime.UtcNow.AddMinutes(30)

After 30 minutes, the access token expires.

Angular sends:

Authorization: Bearer <expired-token>

The API rejects it:

401 Unauthorized

A production application commonly uses:

Access Token
+
Refresh Token

The flow then becomes:

Access Token expires
        ↓
Angular receives 401
        ↓
Send Refresh Token
        ↓
Web API validates Refresh Token
        ↓
Generate new Access Token
        ↓
Angular retries/continues requests

That is more complete than the simple demo above.


14. 401 vs 403

This is an important interview distinction.

401 Unauthorized

Authentication failed.

Examples:

JWT missing
JWT invalid
JWT expired

Result:

401 Unauthorized

403 Forbidden

The user is authenticated, but does not have permission.

For example:

[Authorize(Roles = "Admin")]

User's token contains:

Role = User

The token itself may be valid, but the user isn't authorized for that resource.

Result:

403 Forbidden

So remember:

401 → Who are you? Authentication problem

403 → I know who you are,
      but you don't have permission.

15. Key Points

  • JWT is commonly used for token-based authentication between Angular and ASP.NET Core APIs.
  • Angular sends credentials to a login endpoint.
  • ASP.NET Core validates the credentials.
  • ASP.NET Core generates a signed JWT.
  • JWT can contain claims such as user ID, username, and roles.
  • Angular receives the access token.
  • An Angular HTTP interceptor can attach the token automatically.
  • The standard header is:
Authorization: Bearer <token>
  • ASP.NET Core JWT Bearer authentication validates the token.
  • [Authorize] protects API endpoints.
  • [Authorize(Roles = "Admin")] performs role-based authorization.
  • An expired or invalid token normally results in 401.
  • An authenticated user without sufficient authorization normally gets 403.
  • Production systems commonly use short-lived access tokens and, where appropriate, refresh tokens.
  • Always use HTTPS.
  • Do not store plaintext passwords.
  • Protect signing keys using a proper secret-management mechanism.
  • Browser token storage requires careful XSS/CSRF threat modeling; localStorage is convenient but is not automatically the safest choice for every application.

16. Interview Questions and Answers

1. How does JWT authentication work between Angular and ASP.NET Core Web API?

Angular sends login credentials to the Web API. After validating the user, the API generates a signed JWT. Angular sends that JWT in the Authorization: Bearer <token> header for subsequent requests, and ASP.NET Core validates it before allowing access to protected endpoints.


2. Where is JWT generated?

The JWT is normally generated by the backend authentication system, not by Angular.

ASP.NET Core / Identity Provider
        ↓
Generate JWT
        ↓
Angular receives JWT

3. How does Angular send JWT to the API?

Using the HTTP Authorization header:

Authorization: Bearer <token>

An Angular interceptor is commonly used to add this automatically.


4. Why use an Angular HTTP interceptor?

It centralizes authentication-header logic.

Instead of doing this in every service:

Authorization: `Bearer ${token}`

the interceptor handles it automatically.


5. What does [Authorize] do?

[Authorize]

restricts an endpoint to users who satisfy the configured authorization requirements, with authentication typically established by the JWT bearer handler.


6. What is stored inside a JWT?

A JWT can contain claims such as:

User ID
Username
Role
Issuer
Audience
Expiration

Sensitive secrets such as passwords should not be placed in the JWT payload.


7. Is a JWT encrypted?

Not normally.

A typical JWT used in this flow is signed, not encrypted. Its payload can generally be decoded by anyone who possesses the token.

The signature protects integrity/authenticity, not confidentiality.


8. What happens when JWT expires?

The API rejects the expired access token, typically with:

401 Unauthorized

The application can require the user to log in again or use a refresh-token flow.


9. What is the difference between 401 and 403?

401 → Authentication failed.

403 → Authentication succeeded,
      but authorization failed.

10. Should Angular validate JWT security?

Angular can inspect expiration or claims for UI behavior, such as hiding an Admin menu.

But Angular must never be the security boundary.

The ASP.NET Core API must enforce authentication and authorization.

Angular guard
      ↓
User experience/navigation

ASP.NET Core [Authorize]
      ↓
Actual security

11. Can a route guard replace [Authorize]?

No.

An Angular route guard only controls client-side navigation. A user can bypass the Angular application and call the API directly.

Therefore:

Angular Guard       → UI/navigation protection
ASP.NET [Authorize] → Server-side API protection

The backend authorization is mandatory for real security.


12. Why are issuer and audience validated?

Issuer verifies who issued the token.

Audience verifies whom the token was intended for.

Issuer   → Who created this token?
Audience → Who should accept this token?

13. What is the JWT signing key used for?

The signing key is used to create or validate the token's cryptographic signature.

It helps the API detect whether the token was modified or was not signed by a trusted issuer.


Interview Quick Revision

The entire concept can be remembered as:

1. Angular sends username/password
                ↓
2. ASP.NET Core validates user
                ↓
3. API generates signed JWT
                ↓
4. Angular receives token
                ↓
5. Angular interceptor attaches token
                ↓
6. Authorization: Bearer <JWT>
                ↓
7. ASP.NET Core validates JWT
                ↓
8. [Authorize] checks access
                ↓
9. Controller executes

One-line interview answer:

Angular authenticates the user against the ASP.NET Core Web API, receives a JWT, and sends that token in the Bearer authorization header on subsequent requests. ASP.NET Core validates the token and uses [Authorize] and claims/roles to control access to protected API endpoints.

← Back to Articles