← Back to Articles         
Angular Gets 401 Unauthorized from ASP.NET Core

Angular Gets 401 Unauthorized from ASP.NET Core

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

Angular Gets 401 Unauthorized from ASP.NET Core — How to Troubleshoot

1. What is 401 Unauthorized?

A 401 Unauthorized response means the ASP.NET Core API could not successfully authenticate the request.

In a typical Angular + ASP.NET Core JWT application:

Angular
   |
   | Authorization: Bearer <JWT>
   v
ASP.NET Core Web API
   |
   |-- Is token present?
   |-- Is token valid?
   |-- Is token expired?
   |-- Is signature valid?
   |-- Are issuer/audience valid?
   v
Controller

A useful distinction:

Status Meaning
401 Unauthorized Authentication failed — API cannot establish who the caller is
403 Forbidden Authentication succeeded, but the caller lacks permission

So for a 401, troubleshoot authentication first, not Angular routing or UI authorization.


2. Troubleshooting Order

I would investigate a 401 in this order:

1. Did login succeed?
        ↓
2. Did Angular receive a JWT?
        ↓
3. Is Angular storing/retaining the token correctly?
        ↓
4. Is Angular sending Authorization: Bearer <token>?
        ↓
5. Is an interceptor modifying or omitting it?
        ↓
6. Is the token expired?
        ↓
7. Does ASP.NET Core validate the same issuer/audience/signing key?
        ↓
8. Is authentication middleware configured correctly?
        ↓
9. Is [Authorize] requiring a policy/scheme that differs?

The browser Network tab is usually the fastest starting point.


3. Full Simple Sample Program

Assume we have:

  • Angular frontend
  • ASP.NET Core Web API
  • JWT authentication
  • /api/auth/login returns a token
  • /api/products requires authentication

ASP.NET Core — appsettings.json

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

ASP.NET Core — 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();

The order is important:

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

Authentication should happen before authorization.


4. Login API

AuthController.cs

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

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

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

public record LoginRequest(
    string Username,
    string Password);

5. Protected API

ProductsController.cs

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

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

Because of:

[Authorize]

the endpoint requires authentication.


6. Angular Login Service

auth.service.ts

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

interface LoginResponse {
  token: string;
}

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

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

  constructor(private http: HttpClient) {}

  login(username: string, password: string) {

    return this.http
      .post<LoginResponse>(
        `${this.apiUrl}/auth/login`,
        {
          username,
          password
        }
      )
      .pipe(
        tap(response => {
          localStorage.setItem(
            'access_token',
            response.token
          );
        })
      );
  }

  getToken(): string | null {
    return localStorage.getItem('access_token');
  }

  logout(): void {
    localStorage.removeItem('access_token');
  }
}

After login, Angular stores:

access_token

7. Angular JWT Interceptor

Using a modern functional 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);
  };

The important part is:

Authorization: `Bearer ${token}`

The HTTP request should look like:

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

8. Register the Interceptor

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
      ])
    )

  ]
};

Without registering the interceptor:

withInterceptors([
  authInterceptor
])

your interceptor will never run.

That can easily result in:

401 Unauthorized

9. Angular Product Service

product.service.ts

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

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

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

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

  constructor(private http: HttpClient) {}

  getProducts() {
    return this.http.get<Product[]>(
      this.apiUrl
    );
  }
}

You don't need to manually add the JWT here.

The interceptor automatically converts:

GET /api/products

into:

GET /api/products

Authorization: Bearer eyJ...

10. First Check — Browser Network Tab

This is the first thing I would check.

Open:

F12
  ↓
Network
  ↓
Select failing request
  ↓
Headers

Look at Request Headers.

You should see:

Authorization: Bearer eyJhbGciOi...

If you see no Authorization header, the problem is probably on the Angular side.

Check:

Login
 ↓
Token received?
 ↓
Token retained?
 ↓
Interceptor running?
 ↓
Authorization header attached?

11. Check Whether Angular Has the Token

Temporarily inspect:

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

console.log(token);

Expected:

eyJhbGciOiJIUzI1NiIsInR5cCI...

If:

null

then investigate login/token storage before investigating the API.

For production security design, token storage deserves separate consideration; this example uses localStorage only to keep the authentication flow simple.


12. Check the Bearer Prefix

This is correct:

Authorization: `Bearer ${token}`

This is wrong:

Authorization: token

Also wrong:

Authorization: `JWT ${token}`

Expected format:

Authorization: Bearer <JWT>

Notice the space after:

Bearer

13. Check Token Expiration

JWTs commonly contain:

{
  "exp": 1790952000
}

ASP.NET Core has:

ValidateLifetime = true

If the token is expired, authentication fails.

The application normally needs a strategy such as:

Access Token expires
        ↓
Refresh Token
        ↓
Request new Access Token
        ↓
Retry / continue API calls

Or the application can redirect the user to login when authentication can no longer be refreshed.


14. Check Issuer

Suppose the token was generated using:

issuer: "MyApi"

Then validation must expect:

ValidIssuer = "MyApi"

This mismatch fails:

Token:

iss = MyApi

API expects:

ValidIssuer = DifferentApi

Result:

401

15. Check Audience

Token:

audience: "MyAngularApp"

Validation:

ValidAudience = "MyAngularApp"

If they don't match, token validation fails.

For example:

JWT
aud = MyAngularApp

API
ValidAudience = PortfolioAngularApp

        ↓

Audience validation fails

        ↓

401

16. Check the Signing Key

This is extremely important.

Token generation:

var key =
    new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(jwtKey));

Token validation:

IssuerSigningKey =
    new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(jwtKey));

They must use the same signing secret/key material.

For example:

Token generated using:

ABC123...


API validates using:

XYZ456...

        ↓

Invalid signature

        ↓

401

17. Check ASP.NET Core Middleware

Make sure you have:

app.UseAuthentication();

app.UseAuthorization();

in the appropriate middleware order.

Conceptually:

HTTP Request
     ↓
Authentication
     ↓
Create ClaimsPrincipal
     ↓
Authorization
     ↓
Controller

If authentication isn't configured/executed correctly, [Authorize] cannot authenticate the request.


18. Check Authentication Scheme

Configuration should typically specify JWT Bearer as the authentication scheme:

builder.Services
    .AddAuthentication(
        JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(...);

That tells ASP.NET Core that JWT Bearer authentication is the default scheme.

In applications with multiple schemes—such as cookies plus JWT—scheme selection becomes especially important.


19. Check [Authorize]

Simple authentication:

[Authorize]

But you might have:

[Authorize(Roles = "Admin")]

or:

[Authorize(Policy = "AdminOnly")]

or a specific scheme:

[Authorize(
    AuthenticationSchemes =
        JwtBearerDefaults.AuthenticationScheme)]

Check what the endpoint actually requires.

Remember, a caller that is successfully authenticated but lacks the required role/policy will normally result in 403, whereas failure to authenticate produces 401.


20. Test the API Independently

A very useful isolation technique is to test the JWT outside Angular.

For example:

Login API
    ↓
Copy JWT
    ↓
Swagger / Postman
    ↓
Authorization: Bearer <token>
    ↓
GET /api/products

Scenario 1 — Postman works, Angular fails

Then investigate:

Angular
 ├── token storage/retention
 ├── interceptor
 ├── Authorization header
 └── request configuration

Scenario 2 — Postman also returns 401

Then investigate:

ASP.NET Core
 ├── JWT configuration
 ├── issuer
 ├── audience
 ├── signing key
 ├── expiration
 ├── authentication scheme
 └── middleware

This is an excellent debugging technique because it separates the frontend from the backend.


21. Enable JWT Authentication Diagnostics

During development, you can add JWT Bearer events:

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

    options.Events =
        new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                Console.WriteLine(
                    $"JWT authentication failed: " +
                    $"{context.Exception.Message}");

                return Task.CompletedTask;
            },

            OnTokenValidated = context =>
            {
                Console.WriteLine(
                    "JWT validated successfully.");

                return Task.CompletedTask;
            }
        };
});

This can reveal problems such as:

token expired
invalid audience
invalid issuer
signature validation failed

Avoid returning detailed token-validation errors to clients in production; keep diagnostics in secure server-side logs.


22. What About CORS?

Don't automatically assume:

401 = CORS

They are different problems.

401

Authentication failed

CORS

Browser's cross-origin access rules rejected/blocked the interaction

However, browser requests can involve both CORS and authentication, so inspect the Network tab carefully, including whether a preflight request is failing.


23. Add a Global Angular 401 Handler

You can also handle 401 centrally.

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) {

          localStorage.removeItem(
            'access_token'
          );

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

        return throwError(() => error);
      })
    );
  };

Register both:

provideHttpClient(
  withInterceptors([
    authInterceptor,
    errorInterceptor
  ])
)

Now the flow becomes:

Angular request
      ↓
authInterceptor
      ↓
Add JWT
      ↓
ASP.NET Core
      ↓
401
      ↓
errorInterceptor
      ↓
Clear invalid token
      ↓
Login page

In a refresh-token application, you would usually attempt a controlled token refresh before forcing login.


24. Practical Troubleshooting Checklist

When Angular gets a 401, I would check:

Check What to verify
Login Did authentication succeed?
Token response Did API actually return a JWT?
Token retention Is Angular retaining the correct token?
Network tab Is Authorization present?
Header format Bearer <token>
Interceptor Is it registered and executing?
Expiration Has JWT expired?
Issuer Token iss matches API
Audience Token aud matches API
Signature Correct signing key
Scheme JWT Bearer configured correctly
Middleware Authentication before authorization
Endpoint Correct [Authorize] requirements
Independent test Does same token work in Swagger/Postman?
Server logs What specifically caused JWT validation failure?

Key Points

  • 401 primarily means authentication failed.
  • Start with the browser Network tab.
  • Verify:
Authorization: Bearer <token>
  • If the header is missing, investigate Angular.
  • If the header is present, investigate token validity and ASP.NET Core JWT configuration.
  • Verify:
    • expiration
    • issuer
    • audience
    • signature/signing key
    • authentication scheme
  • Test the same token with Swagger/Postman to isolate frontend vs backend.
  • Use server-side JWT authentication diagnostics during development.
  • 401 and 403 are different:
401 → Who are you? Authentication failed.

403 → I know who you are, but you aren't allowed.

Interview Questions and Answers

1. Your Angular application gets a 401 from ASP.NET Core. How would you troubleshoot it?

Answer:

I would first inspect the browser Network tab and verify that Angular is sending Authorization: Bearer <token>. Then I would check whether the token is expired and verify the issuer, audience, signing key, and JWT Bearer configuration in ASP.NET Core. Finally, I would test the same token through Swagger or Postman to determine whether the issue is in Angular or the API.


2. What is the difference between 401 and 403?

Answer:

401 indicates that authentication failed or valid authentication credentials were not supplied.

403 means the caller is authenticated but does not have sufficient authorization to access the resource.


3. Postman works, but Angular gets 401. What would you check?

Answer:

I would primarily investigate the Angular request:

Token available?
      ↓
Interceptor registered?
      ↓
Authorization header added?
      ↓
Correct Bearer format?

I would compare Angular's actual request headers against the working Postman request.


4. Both Angular and Postman return 401. Where is the likely problem?

Answer:

That points more strongly toward the token or backend authentication configuration. I would verify expiration, issuer, audience, signing key, authentication scheme, and JWT Bearer configuration.


5. Can an expired JWT produce 401?

Yes.

When:

ValidateLifetime = true

ASP.NET Core rejects an expired token during authentication.


6. Should Angular manually attach JWTs in every service?

Usually no.

A centralized HTTP interceptor is cleaner:

Authorization: `Bearer ${token}`

This avoids duplicating authentication code across services.


7. Should Angular route guards be used instead of ASP.NET Core [Authorize]?

No.

Angular guards improve client-side navigation and user experience, but they do not provide backend security.

The API must independently enforce authorization:

[Authorize]

The important architecture is:

Angular Guard
    ↓
Client-side navigation control

Angular Interceptor
    ↓
Attach JWT

ASP.NET Core Authentication
    ↓
Validate JWT

ASP.NET Core Authorization
    ↓
Enforce roles/policies

Controller

The backend remains the security boundary.

← Back to Articles