ASP.NET Web API (.NET Framework) · Cross-Cutting

Role-Based Authorization

Authentication answers “who are you?”. Authorization answers “what may you do?”. Roles — Admin, Manager, Customer — are the simplest way to decide who can reach which endpoints.

Intermediate 6 min Lesson 24 of 36Covers ASP.NET Web API 2.2 on .NET Framework 4.8.
Before you start:Token Authentication
0/36 in ASP.NET Web API
On this page · 6 min · Intermediate

Learning objectives#

  • Restrict controllers and actions with [Authorize(Roles = ...)]
  • Understand how multiple Authorize attributes combine
  • Read roles from claims and check User.IsInRole
  • Return 403 Forbidden for signed-in users without the role

Explanation#

Roles are claims in the token (ClaimTypes.Role), added when the token was issued.

[Authorize(Roles = "Admin,Manager")] allows users in ANY of the listed roles. Stacking two [Authorize] attributes (controller + action) requires BOTH to pass.

Inside an action, User.IsInRole("Admin") lets you vary behaviour, such as showing cost prices only to admins.

Web API 2’s AuthorizeAttribute returns 401 both for anonymous users and for signed-in users without the role. Override HandleUnauthorizedRequest to return 403 for the second case, which clients can handle correctly.

Roles cannot express everything. “Users may only see their own orders” is a resource check in code: compare the order’s owner with the current user and return 403 or 404.

Syntax#

C# Illustrative
[Authorize(Roles = "Admin,Manager")]      // Admin OR Manager
public class ReportsController : ApiController
{
    [Authorize(Roles = "Admin")]           // AND additionally Admin
    public IHttpActionResult Delete(int id) { ... }
}

Code example: Admin endpoints and a 403-aware attribute#

C#AdminProductsController.cs Illustrative
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Web.Http;
using System.Web.Http.Controllers;


// Returns 403 (not 401) when the user is signed in but lacks the role
public class RoleAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        var principal = actionContext.RequestContext.Principal;
        if (principal != null && principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(
                HttpStatusCode.Forbidden, "You do not have permission to do this.");
        }
        else
        {
            base.HandleUnauthorizedRequest(actionContext);   // 401
        }
    }
}


[RoutePrefix("api/admin/products")]
[RoleAuthorize(Roles = "Admin,Manager")]
public class AdminProductsController : ApiController
{
    [HttpGet, Route("")]
    public IHttpActionResult GetAll()
    {
        bool showCost = User.IsInRole("Admin");
        return Ok(new[] { new { Name = "Pen", Price = 20m, Cost = showCost ? (decimal?)12m : null } });
    }


    [HttpDelete, Route("{id:int}")]
    [RoleAuthorize(Roles = "Admin")]                // Manager is not enough here
    public IHttpActionResult Delete(int id)
    {
        return StatusCode(HttpStatusCode.NoContent);
    }
}


public class MeController : ApiController
{
    [Authorize]
    [HttpGet, Route("api/me")]
    public IHttpActionResult Get()
    {
        var identity = (ClaimsIdentity)User.Identity;
        var roles = identity.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
        return Ok(new { name = identity.Name, roles });
    }
}
Expected output
GET    /api/admin/products     (no token)        → 401
GET    /api/admin/products     (Customer token)  → 403 {"Message":"You do not have permission to do this."}
GET    /api/admin/products     (Manager token)   → 200 [{"Name":"Pen","Price":20.0,"Cost":null}]
GET    /api/admin/products     (Admin token)     → 200 [{"Name":"Pen","Price":20.0,"Cost":12.0}]
DELETE /api/admin/products/1   (Manager token)   → 403
DELETE /api/admin/products/1   (Admin token)     → 204
GET    /api/me                 (Manager token)   → 200 {"name":"ravi","roles":["Manager"]}

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. RoleAuthorizeAttribute inherits AuthorizeAttribute and only changes what happens on failure.
  2. The controller allows Admin or Manager; Delete adds a second attribute, so only Admin passes both.
  3. User.IsInRole("Admin") hides the cost price from managers without needing a separate endpoint.
  4. MeController reads all role claims from the ClaimsIdentity — useful for front ends deciding which menus to show.

Real-world use case#

A back-office API lets Support staff view orders, Managers issue refunds up to ₹5,000 (checked in the service), and only Admins delete products. Each rule is one attribute or one check, easy to audit.

Common mistakes#

Spaces and case in role names

Roles = "Admin, Manager" works because Web API trims, but a typo like "admin" vs "Admin" silently denies access. Use constants.

Hiding buttons instead of protecting endpoints

Hiding the Delete button in the UI does not stop someone calling DELETE directly. Authorize on the server.

Forgetting resource ownership

A Customer role does not mean “any customer’s order”. Check that the order belongs to the current user.

Best practices#

  • Define role names as constants
  • Authorize at controller level, tighten per action
  • Return 403 for authenticated-but-forbidden
  • Combine roles with resource ownership checks

Remember this#

Roles = "A,B" → A OR B. Two attributes → BOTH.

401 = not signed in. 403 = signed in, not allowed.

Roles say what kind of user; ownership checks say which data.

Practice exercise#

Your task: Write GET api/orders/{id} that returns 404 if the order does not exist, 403 if it belongs to another user (unless the caller is Admin), and 200 otherwise. Orders are a static Dictionary<int, string> of id → owner user name.

Need a hint?
Compare owner with User.Identity.Name and allow User.IsInRole("Admin").
Show solution
C# Illustrative
using System.Collections.Generic;
using System.Net;
using System.Web.Http;


[Authorize]
public class OrdersController : ApiController
{
    private static readonly Dictionary<int, string> Owners = new Dictionary<int, string> { [1] = "asha", [2] = "ravi" };


    [HttpGet, Route("api/orders/{id:int}")]
    public IHttpActionResult Get(int id)
    {
        string owner;
        if (!Owners.TryGetValue(id, out owner)) return NotFound();
        if (owner != User.Identity.Name && !User.IsInRole("Admin"))
            return StatusCode(HttpStatusCode.Forbidden);
        return Ok(new { id, owner });
    }
}

Mini quiz#

Role-Based Authorization quizMultiple choice Question 1 of 3

What does [Authorize(Roles = "Admin,Manager")] allow?

Score so far: 0/0

Want more? Practise all ASP.NET Web API MCQs · ASP.NET Web API interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.