.NET Core  

JWT Session 6: Protecting ASP.NET Core Web APIs Using the [Authorize] Attribute

In the previous article, we configured JWT Bearer Authentication and learned how ASP.NET Core validates incoming JWTs before processing a request.

At this stage, our API can:

  • Generate JWTs after a successful login.

  • Validate JWTs on incoming requests.

However, there is still a problem.

Even though authentication is configured, all API endpoints are still accessible unless we explicitly protect them.

This is where the [Authorize] attribute comes into play.

In this article, we'll learn how to secure ASP.NET Core Web API endpoints using the [Authorize] attribute, understand how it works internally, and see how to allow both public and protected endpoints in the same application.

Protecting ASP.NET Core Web APIs Using the [Authorize] Attribute

What Is the [Authorize] Attribute?

The [Authorize] attribute tells ASP.NET Core that an endpoint can only be accessed by an authenticated user.

If a request reaches an endpoint marked with [Authorize], ASP.NET Core first checks whether the user has been successfully authenticated.

If authentication succeeds, the request continues.

If authentication fails, the request is rejected immediately.

Simply put,

[Authorize] acts like a security gate in front of your API endpoint.

Why Do We Need [Authorize]?

Consider our SecureShop API.

Some endpoints should be available to everyone.

Examples:

  • Product catalog

  • Login

  • Registration

Other endpoints should only be available after login.

Examples:

  • Customer Profile

  • Order History

  • Shopping Cart

  • Wishlist

Without endpoint protection, anyone could access customer data simply by knowing the API URL.

The [Authorize] attribute prevents this.

Public vs Protected Endpoints

A typical Web API contains both public and protected endpoints.

EndpointAuthentication Required
POST /api/auth/login❌ No
POST /api/auth/register❌ No
GET /api/products❌ No
GET /api/profile✅ Yes
GET /api/orders✅ Yes
POST /api/orders✅ Yes

This is why authentication alone isn't enough.

You must also decide which endpoints require authentication.

How Does [Authorize] Work?

Let's understand the complete flow.

HTTP Request
      │
      ▼
Authentication Middleware
      │
      ▼
JWT Validation
      │
      ▼
User Authenticated?
      │
  Yes │ No
      │
      ▼
[Authorize]
      │
      ▼
Controller Action

Notice that [Authorize] does not validate the JWT itself.

JWT validation has already been completed by the Authentication Middleware.

The attribute simply checks whether an authenticated user exists.

Step 1: Create a Protected Controller

Let's create a controller for customer profiles.

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

[ApiController]
[Route("api/profile")]
public class ProfileController : ControllerBase
{
    [Authorize]
    [HttpGet]
    public IActionResult GetProfile()
    {
        return Ok("Customer Profile");
    }
}

Now this endpoint requires a valid JWT.

If the request does not contain a valid token, ASP.NET Core returns:

401 Unauthorized

before the action method executes.

Step 2: Keep Public Endpoints Open

Not every endpoint should require authentication.

For example, users must be able to log in before they receive a JWT.

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    [HttpPost("login")]
    public IActionResult Login()
    {
        // Generate JWT
    }
}

Notice that the login endpoint does not have the [Authorize] attribute.

Otherwise, users would need a JWT before they could log in—which is impossible.

Step 3: Protect an Entire Controller

Instead of decorating every action individually, you can secure the entire controller.

[Authorize]
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetOrders()
    {
        return Ok();
    }

    [HttpPost]
    public IActionResult CreateOrder()
    {
        return Ok();
    }
}

Now every action inside the controller requires authentication.

This reduces duplication and keeps your code cleaner.

Step 4: Allow Anonymous Access

Sometimes a protected controller contains one action that should remain public.

ASP.NET Core provides the [AllowAnonymous] attribute for this purpose.

[Authorize]
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    [AllowAnonymous]
    [HttpGet]
    public IActionResult GetProducts()
    {
        return Ok();
    }

    [HttpPost]
    public IActionResult AddProduct()
    {
        return Ok();
    }
}

In this example:

  • Anyone can view products.

  • Only authenticated users can add products.

[AllowAnonymous] overrides [Authorize] for that specific action.

Internal Request Flow

Let's see what happens when a client calls a protected endpoint.

Client
   │
   │ GET /api/profile
   │ Bearer Token
   ▼
Authentication Middleware
   │
   ▼
Validate JWT
   │
   ▼
Authenticated User Created
   │
   ▼
[Authorize]
   │
   ▼
Controller Action
   │
   ▼
HTTP Response

If the JWT is missing or invalid, the request stops before reaching the controller.

Practical Example

Let's continue with our SecureShop API.

Login

POST /api/auth/login
↓
The API returns a JWT.
↓
Request Customer Profile
↓
GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
↓
Authentication Middleware validates the JWT.
↓
[Authorize] confirms the user is authenticated.
↓
The controller executes successfully.

Now imagine the same request without a JWT.

GET /api/profile

Since no authenticated user exists, ASP.NET Core immediately responds with:

401 Unauthorized

The controller action is never executed.

Common Mistakes

Mistake 1: Forgetting the [Authorize] Attribute

Many developers configure JWT authentication correctly but forget to add [Authorize].

As a result, every endpoint remains publicly accessible.

Mistake 2: Protecting the Login Endpoint

Applying [Authorize] to the login action prevents users from logging in.

Keep authentication endpoints public.

Mistake 3: Expecting [Authorize] to Validate Tokens

The [Authorize] attribute does not validate JWTs.

Authentication Middleware performs validation before the request reaches the controller.

Mistake 4: Using [AllowAnonymous] Unnecessarily

Avoid adding [AllowAnonymous] unless an endpoint genuinely needs to be public.

Every anonymous endpoint increases your application's exposed surface.

Key Takeaways

  • [Authorize] protects API endpoints by allowing access only to authenticated users.

  • JWT validation happens before [Authorize] is evaluated.

  • Apply [Authorize] at either the action level or the controller level.

  • Use [AllowAnonymous] for endpoints that should remain public.

  • Authentication identifies the user, while [Authorize] ensures only authenticated users can access protected resources.

At this point, our SecureShop API can generate JWTs, validate them, and protect endpoints. In the next article, we'll explore how to access the logged-in user's information using HttpContext.User and Claims, allowing us to identify exactly who is making each request without querying the database on every API call.