← Back to Articles         
Login Using Angular + ASP.NET Core JWT Authentication

Login Using Angular + ASP.NET Core JWT Authentication

Published on 02 Oct 2026     12 min read Angular
Scenario Based Questions

Login Using Angular + ASP.NET Core JWT Authentication

1. What is JWT Login?

In an Angular + ASP.NET Core Web API application, Angular sends the user's credentials to the API. ASP.NET Core validates them and, if valid, generates a JSON Web Token (JWT).

Angular then sends that token with protected API requests:

Authorization: Bearer <JWT>

The overall flow is:

Angular Login Form
        ↓
POST /api/auth/login
        ↓
ASP.NET Core validates credentials
        ↓
Generate JWT
        ↓
Return JWT to Angular
        ↓
Angular retains token
        ↓
HTTP Interceptor attaches token
        ↓
Authorization: Bearer <token>
        ↓
ASP.NET Core validates JWT
        ↓
[Authorize]
        ↓
Protected API

For a simple learning example, I'll use hard-coded credentials and localStorage. In a production application, credentials should come from a database with properly hashed passwords, and token storage/refresh strategy should be designed carefully.


2. Project Structure

ASP.NET Core

MyApi
│
├── Controllers
│   ├── AuthController.cs
│   └── ProductsController.cs
│
├── Models
│   └── LoginRequest.cs
│
├── appsettings.json
└── Program.cs

Angular

src/app
│
├── login
│   ├── login.ts
│   └── login.html
│
├── products
│   ├── products.ts
│   └── products.html
│
├── services
│   └── auth.service.ts
│
├── interceptors
│   └── auth.interceptor.ts
│
├── guards
│   └── auth.guard.ts
│
├── app.routes.ts
└── app.config.ts

Part 1 — ASP.NET Core Web API

3. Install JWT Package

Install:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

4. JWT Configuration

appsettings.json

{
  "Jwt": {
    "Key": "ThisIsMySuperSecretKeyForJwtAuthentication12345",
    "Issuer": "MyApi",
    "Audience": "MyAngularApp",
    "ExpiryMinutes": 30
  }
}

These values mean:

Setting Purpose
Key Signs the JWT
Issuer Who generated the token
Audience Who the token is intended for
ExpiryMinutes Token lifetime

The signing key should be stored securely in production rather than committed to source control.


5. Login Request Model

LoginRequest.cs

namespace MyApi.Models;

public class LoginRequest
{
    public string Username { get; set; } = "";
    public string Password { get; set; } = "";
}

Angular will send:

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

6. Configure 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.AddCors(options =>
{
    options.AddPolicy("Angular", policy =>
    {
        policy
            .WithOrigins("http://localhost:4200")
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

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("Angular");

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

app.MapControllers();

app.Run();

The critical part is:

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

Authentication identifies the caller; authorization determines whether that authenticated caller can access a resource.


7. Create the Login API

AuthController.cs

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

namespace MyApi.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.
        if (request.Username != "admin" ||
            request.Password != "123")
        {
            return Unauthorized(new
            {
                message = "Invalid username or password"
            });
        }

        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 expiryMinutes =
            int.Parse(
                _configuration["Jwt:ExpiryMinutes"]!);

        var token =
            new JwtSecurityToken(
                issuer:
                    _configuration["Jwt:Issuer"],

                audience:
                    _configuration["Jwt:Audience"],

                claims: claims,

                expires:
                    DateTime.UtcNow
                        .AddMinutes(expiryMinutes),

                signingCredentials:
                    credentials);

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

        return Ok(new
        {
            token = tokenString
        });
    }
}

8. What Happens During Login?

Angular sends:

POST /api/auth/login
Content-Type: application/json

Body:

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

ASP.NET Core checks:

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

If credentials are correct, claims are created:

new Claim(
    ClaimTypes.Name,
    request.Username),

new Claim(
    ClaimTypes.Role,
    "Admin")

The JWT therefore contains identity information such as:

User = admin
Role = Admin

9. JWT Generation

The signing key:

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

Signing credentials:

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

Then:

var token =
    new JwtSecurityToken(
        issuer: "...",
        audience: "...",
        claims: claims,
        expires: ...,
        signingCredentials: credentials);

Finally:

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

Response:

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

10. Create a Protected API

ProductsController.cs

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

namespace MyApi.Controllers;

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

Because of:

[Authorize]

this request requires a valid JWT.

Without a valid token:

401 Unauthorized

With a valid token:

200 OK

Part 2 — Angular

11. Configure HttpClient

Modern standalone Angular uses:

provideHttpClient()

instead of importing HttpClientModule into an AppModule.

app.config.ts

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

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

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

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

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

export const appConfig: ApplicationConfig = {

  providers: [

    provideRouter(routes),

    provideHttpClient(
      withInterceptors([
        authInterceptor
      ])
    )

  ]
};

12. Create Angular Authentication Service

auth.service.ts

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

interface LoginRequest {
  username: string;
  password: string;
}

interface LoginResponse {
  token: string;
}

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

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

  constructor(
    private http: HttpClient
  ) {}

  login(username: string, password: string) {

    const request: LoginRequest = {
      username,
      password
    };

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

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

        })
      );
  }

  getToken(): string | null {

    return localStorage.getItem(
      'access_token'
    );
  }

  isLoggedIn(): boolean {

    return !!this.getToken();
  }

  logout(): void {

    localStorage.removeItem(
      'access_token'
    );
  }
}

13. Login Component

login.ts

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';

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

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [
    FormsModule
  ],
  templateUrl: './login.html'
})
export class Login {

  username = '';
  password = '';
  errorMessage = '';

  constructor(
    private authService: AuthService,
    private router: Router
  ) {}

  login(): void {

    this.errorMessage = '';

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

        next: () => {

          this.router.navigate([
            '/products'
          ]);

        },

        error: error => {

          if (error.status === 401) {

            this.errorMessage =
              'Invalid username or password';

          } else {

            this.errorMessage =
              'Login failed';
          }
        }
      });
  }
}

14. Login HTML

login.html

<h2>Login</h2>

<form (ngSubmit)="login()">

  <div>
    <label>Username</label>

    <input
      type="text"
      name="username"
      [(ngModel)]="username">
  </div>

  <div>
    <label>Password</label>

    <input
      type="password"
      name="password"
      [(ngModel)]="password">
  </div>

  <button type="submit">
    Login
  </button>

</form>

@if (errorMessage) {
  <p>{{ errorMessage }}</p>
}

Enter:

Username: admin
Password: 123

Angular sends:

POST https://localhost:7001/api/auth/login

15. What Happens After Successful Login?

ASP.NET Core returns:

{
  "token": "eyJhbGciOiJIUzI1Ni..."
}

The service executes:

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

Now:

Browser
   ↓
localStorage
   ↓
access_token
   ↓
eyJhbGciOiJIUzI1Ni...

16. Create JWT Interceptor

We don't want every Angular service to manually do this:

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

Instead, use an interceptor.

auth.interceptor.ts

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

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

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

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

    const authRequest =
      req.clone({

        setHeaders: {

          Authorization:
            `Bearer ${token}`

        }
      });

    return next(authRequest);
  };

Now Angular automatically adds:

Authorization: Bearer eyJhbGciOi...

17. Product Component

products.ts

import {
  Component,
  OnInit
} from '@angular/core';

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

interface Product {
  id: number;
  name: string;
  price: number;
}

@Component({
  selector: 'app-products',
  standalone: true,
  templateUrl: './products.html'
})
export class Products implements OnInit {

  products: Product[] = [];

  constructor(
    private http: HttpClient
  ) {}

  ngOnInit(): void {

    this.http
      .get<Product[]>(
        'https://localhost:7001/api/products'
      )
      .subscribe(products => {

        this.products = products;

      });
  }
}

Notice that there is no JWT code here.

The interceptor handles it.


18. Product HTML

products.html

<h2>Products</h2>

@for (product of products; track product.id) {

  <div>
    {{ product.name }}
    -
    {{ product.price }}
  </div>

}

19. Create an Authentication Guard

The interceptor attaches the JWT to HTTP requests.

A route guard controls Angular navigation.

auth.guard.ts

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

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

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

export const authGuard: CanActivateFn =
  () => {

    const authService =
      inject(AuthService);

    const router =
      inject(Router);

    if (authService.isLoggedIn()) {

      return true;

    }

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

20. Configure Routes

app.routes.ts

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

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

export const routes: Routes = [

  {
    path: 'login',

    loadComponent: () =>
      import('./login/login')
        .then(m => m.Login)
  },

  {
    path: 'products',

    canActivate: [
      authGuard
    ],

    loadComponent: () =>
      import('./products/products')
        .then(m => m.Products)
  },

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

];

Now:

/products
    ↓
authGuard
    ↓
Logged in?
   / \
 Yes  No
 ↓     ↓
Open   /login

But remember:

Angular route guards are not a security boundary.

A user can manipulate browser-side JavaScript.

Actual security is enforced by:

[Authorize]

on the ASP.NET Core API.


21. Complete Login Flow

The complete architecture is:

             USER
              |
              v
       Angular Login Form
              |
              | username/password
              v
      POST /api/auth/login
              |
              v
      ASP.NET Core API
              |
              | Validate user
              v
         Generate JWT
              |
              v
        Return JWT
              |
              v
       Angular AuthService
              |
              | retain token
              v
       Browser token state
              |
              v
       Navigate /products
              |
              v
          authGuard
              |
              v
        Products component
              |
              | GET /api/products
              v
       authInterceptor
              |
              | Add:
              | Authorization:
              | Bearer <JWT>
              v
       ASP.NET Core API
              |
              v
      JWT Authentication
              |
              v
         [Authorize]
              |
              v
      ProductsController
              |
              v
          200 OK

22. Authentication vs Authorization

This distinction is important in interviews.

Authentication

Who are you?

Example:

Username/password
        ↓
JWT
        ↓
Authenticated user

Authorization

What are you allowed to do?

Example:

[Authorize(Roles = "Admin")]

The user might be authenticated but not be an administrator.

Then the typical response is:

403 Forbidden

23. Role-Based Authorization Example

We created:

new Claim(
    ClaimTypes.Role,
    "Admin")

Therefore we can protect an endpoint:

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

Flow:

JWT
 ↓
Role = Admin
 ↓
[Authorize(Roles = "Admin")]
 ↓
Allowed

24. What About Logout?

JWT authentication is generally stateless.

For our simple example:

logout(): void {

  localStorage.removeItem(
    'access_token'
  );
}

Then redirect:

this.authService.logout();

this.router.navigate([
  '/login'
]);

The browser no longer has the access token to send.

More advanced systems may also revoke/rotate refresh tokens server-side.


25. What About Token Expiration?

Our API creates the token with:

expires:
    DateTime.UtcNow
        .AddMinutes(30)

After 30 minutes:

JWT expires
    ↓
Angular sends expired JWT
    ↓
ASP.NET Core validates lifetime
    ↓
Authentication fails
    ↓
401 Unauthorized

A production application commonly uses:

Short-lived Access Token
          +
Longer-lived Refresh Token

Flow:

Access token expires
        ↓
API returns 401
        ↓
Angular attempts refresh
        ↓
Refresh endpoint
        ↓
New access token
        ↓
Continue requests

26. Important Production Security Consideration

For learning, we used:

localStorage.setItem(
  'access_token',
  token
);

This is simple, but localStorage is accessible to JavaScript running on the page. Therefore, an XSS vulnerability can expose the token.

A production design should deliberately choose its authentication architecture.

For example, depending on the application:

Option A
Short-lived JWT access token
+ refresh-token strategy

Option B
Secure HttpOnly cookies
+ CSRF protection

Don't assume localStorage is automatically the correct production choice simply because it is easy to implement.

Also:

  • Never store passwords in Angular.
  • Never put JWT signing secrets in Angular.
  • Never trust client-side role checks as backend security.
  • Always use HTTPS in production.
  • Hash user passwords server-side using an appropriate password-hashing mechanism.

Key Points

ASP.NET Core responsibilities

Validate username/password
Generate JWT
Sign JWT
Set expiration
Add claims
Validate JWT
Perform authorization
Protect APIs using [Authorize]

Angular responsibilities

Collect credentials
Call login API
Retain authentication state/token
Attach JWT using interceptor
Handle 401 responses
Use guards for navigation UX
Logout

Most important architecture

Angular
   |
   | POST username/password
   v
Login API
   |
   | JWT
   v
Angular
   |
   | Authorization: Bearer JWT
   v
ASP.NET Core Authentication
   |
   v
[Authorize]
   |
   v
Protected API

And remember:

Angular Guard
      =
Navigation convenience

ASP.NET Core [Authorize]
      =
Actual API security

Interview Questions and Answers

1. How would you implement login using Angular and ASP.NET Core JWT authentication?

Interview Answer:

Angular sends the username and password to an ASP.NET Core login endpoint. The API validates the credentials, generates a signed JWT containing the required claims, and returns it. Angular retains the token and an HTTP interceptor attaches it as Authorization: Bearer <token> to protected API requests. ASP.NET Core validates the JWT and protects endpoints using [Authorize].


2. Why use an Angular HTTP interceptor?

An interceptor centralizes token attachment:

Authorization: Bearer <token>

This avoids repeating authentication-header code in every Angular service.


3. What does [Authorize] do?

[Authorize]

requires the request to have a successfully authenticated user.

With JWT Bearer authentication, ASP.NET Core validates the JWT before allowing access.


4. Where should the JWT signing key be stored?

The signing key belongs on the server side only.

Production secrets should normally be stored in a secure configuration mechanism such as environment-specific secret storage or a managed secrets service.

Never place the JWT signing key in Angular.


5. What happens when the JWT expires?

ASP.NET Core rejects the expired access token, normally resulting in:

401 Unauthorized

The application can then authenticate again or use a refresh-token mechanism to obtain a new access token.


6. Should Angular route guards provide security?

No.

Angular Guard
     ↓
Controls client navigation

ASP.NET Core [Authorize]
     ↓
Enforces actual API security

Backend authorization must always be enforced independently.


7. 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.


8. What is the difference between 401 and 403?

401 Unauthorized
→ Authentication failed.

403 Forbidden
→ Authentication succeeded,
  but permission is insufficient.

9. Why shouldn't the JWT signing key be stored in Angular?

Angular code is downloaded to the user's browser and can be inspected.

If the signing key were exposed, an attacker could potentially create forged tokens.

The signing key must remain on the trusted server side.


10. What would you improve for production?

Interview Answer:

I would validate users against a database with securely hashed passwords, use HTTPS, short-lived access tokens, an appropriate refresh-token strategy, secure server-side secret management, backend role/policy authorization, centralized 401 handling, and a deliberate token-storage strategy based on the application's security requirements.

For interview revision, the most important flow to remember is:

Login
 ↓
Validate Credentials
 ↓
Generate JWT
 ↓
Return JWT
 ↓
Angular retains JWT
 ↓
Interceptor adds Bearer token
 ↓
ASP.NET Core validates JWT
 ↓
[Authorize]
 ↓
Protected API
← Back to Articles