In this tutorial, we will show you how to implement basic Authorization using Authorize attribute. The Authorize
attribute restricts the unauthenticated user from accessing the URL. You can override it with AllowAnonymous
attribute. Our example will use both MVC & Razor Pages. The Web API Projects also use the same techniques to protect the API Endpoints.
Table of Contents
We use Authorize
attribute to decorate a controller, action, or Razor page. This will limit access to that only to the authenticated user. The unauthenticated users are redirected to the login page.
For example, the following code limits access to the AccountController
only to the authenticated users.
1 2 3 4 5 6 7 8 9 10 11 12 13 | [Authorize] public class AccountController : Controller { public ActionResult Login() { } public ActionResult Logout() { } } |
Example Project
Create a new ASP.NET Core Web App (MVC) using VS2019. Name the app as AuthzExample
. Choose .NET 5.0 & Individual Accounts.
Update the appsettings.json
with the connection string.
1 2 3 4 5 | "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=AuthzExample;Trusted_Connection=True;MultipleActiveResultSets=true" }, |
Create a new entity Product under the folder Data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using Microsoft.AspNetCore.Identity; using System.ComponentModel.DataAnnotations.Schema; namespace AuthzExample.Data { public class Product { public int ProductId { get; set; } public string Name { get; set; } [ForeignKey("IdentityUser")] public string CreatedUserID { get; set; } public IdentityUser User { get; set; } } } |
Add DbSet Property in ApplicationDbContext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace AuthzExample.Data { public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Product> Products { get; set; } } } |
Run the add-migration
& update-database
to create the database
1 2 3 4 | add-migration product update-database |
To Manage the Products, we will create both MVC & Razor Page versions of the page.
Create a new MVCProductsController
using the template MVC Controller using Views, Using Entity Framework. Model class as Product
& Data Context class as ApplicationDbContext
. Layout Page as ~/Views/Shared/_Layout.cshtml
. Controller Name MVCProductsController
Create a new folder Pages/RazorProducts
. Add a Razor Page using the template Razor Pages using the Entity Framework (CRUD). Model class as Product
& Data Context class as ApplicationDbContext
. Layout page as ~/Views/Shared/_Layout.cshtml
Under the Pages
, folder add the _ViewImports.cshtml
file with the following code
1 2 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
Add the navigation link to both the pages in the _Layout.cshtml
after the Home
menu.
1 2 3 4 5 6 7 8 | <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="MVCProducts" asp-action="Index">Products (MVC)</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/RazorProducts/Index">Products (Razor) </a> </li> |
Open the startup class and add the following code to enable Razor Pages
1 2 3 | services.AddRazorPages(); |
Run the app, you should be able to access all the pages including the Products page without login.
Now, let us secure the Products Page.
Securing a MVC/API Controller
Add the [Authorize]
attribute to the MVCProductsController
class. This will stop the unauthenticated users from accessing the MVCProductsController
.
When we apply the Authorize
class, all the action methods of that controller automatically inherit it. The Authorize attribute lives in the namespace Microsoft.AspNetCore.Authorization;
1 2 3 4 5 | [Authorize] public class MVCProductsController : Controller { |
Now, when you navigate to /MVCProducts
page, you are redirected to /Identity/Account/Login?ReturnUrl=%2FMVCProducts
page.
Note that the MVCProducts
is passed as the ReturnUrl
to the login page. Once you are completed the login you are redirected back to the login page.
Now, what if we allow unauthenticated users to view the Product List & Details page, but restrict Create & Edit Page.
AllowAnonymous Attribute
AllowAnonymous
attribute overrides Authorize
attribute and allows access to unauthenticated users.
Appy the AllowAnonymous
attribute on the Index
action method. Now you can navigate to the Products page. But you cannot navigate to Products/Create
page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [Authorize] public class MVCProductsController : Controller { private readonly ApplicationDbContext _context; public MVCProductsController(ApplicationDbContext context) { _context = context; } // GET: MVCProducts [AllowAnonymous] public async Task<IActionResult> Index() { var applicationDbContext = _context.Products.Include(p => p.User); return View(await applicationDbContext.ToListAsync()); } |
AllowAnonymous
overrides everything.
If we apply the AllowAnonymous
controller class access to all the methods are allowed, even if you decorate the action methods with Authorize
attribute.
In the following example, access to the /Products
is still allowed although we have an Authorize
attribute on it. This is because the AllowAnonymous
on the MVCProductsController
overrides it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [AllowAnonymous] public class MVCProductsController : Controller { private readonly ApplicationDbContext _context; public MVCProductsController(ApplicationDbContext context) { _context = context; } // GET: MVCProducts [Authorize] public async Task<IActionResult> Index() { var applicationDbContext = _context.Products.Include(p => p.User); return View(await applicationDbContext.ToListAsync()); } |
If you apply both AllowAnonymous
and Authorize
attribute, then Authorize
is ignored.
Securing a Razor Page
Apply Authorize
attribute to the Page Model.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | [Authorize] public class CreateModel : PageModel { private readonly AuthzExample.Data.ApplicationDbContext _context; public CreateModel(AuthzExample.Data.ApplicationDbContext context) { _context = context; } public IActionResult OnGet() { ViewData["userId"] = new SelectList(_context.Users, "Id", "Id"); return Page(); } [BindProperty] public Product Product { get; set; } // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } _context.Products.Add(Product); await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } } |
We cannot apply the Authorize
attribute to Razor Page handlers.
For Example, in the code above, we have OnGet
& OnPost
page handlers. We cannot use Authorize
attribute on them.
In case, you want to apply attributes on Razor Page Handlers, then Create a separate page for each page handler
To secure our, we need to Add Authorize
attribute to every controller and page. But it time-consuming and error-prone as It is easier to miss a controller or page.
Fortunately, we can apply authorization globally using the RequireAuthorization
extension method. We apply this method while configuring the end points.
1 2 3 4 5 6 7 8 9 10 11 | app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}" ).RequireAuthorization(); endpoints.MapRazorPages().RequireAuthorization(); }); |
Note, that this will apply the Authorize
attribute on all the pages and controllers. Hence you need to add the AllowAnonymous
attribute to those publically accessible pages. For Example, login, home & register pages.
1 2 3 4 5 | [AllowAnonymous] public class HomeController : Controller { |
Login & Register pages come from the identity API and already has the AllowAnonymous
attribute.
If you are using Razor pages, you can also make use of the Authorization Conventions to set the Authorize
or AllowAnonymous
attributes.
The support for Razor Pages comes from the AddRazorPages
extenton method in the ConfigureServices
method. We can pass addtional options to configure the Razor pages in the method.
By using the AuthorizePage
, AuthorizeFolder
, AllowAnonymousToPage
, AllowAnonymousToFolder
,AuthorizeAreaPage
& AuthorizeAreaFolder
options, we can add the Authorize
or AllowAnonymous
attributes to the pages.
1 2 3 4 5 6 7 8 9 | services.AddRazorPages(options => { options.Conventions.AuthorizePage("/Contact"); options.Conventions.AuthorizeFolder("/Private"); options.Conventions.AllowAnonymousToPage("/Private/PublicPage"); options.Conventions.AllowAnonymousToFolder("/Private/PublicPages"); }); |
Applying the method names that ends with Folder
(ex: AuthorizeFolder
or AuthorizeAreaFolder
) will apply the attribute to all the pages under the folder. While method names that end with Page
will apply the attribute to only that page.
Reference
Read More
- ASP.NET Core Tutorial
- Authentication in ASP.NET Core
- Cookie Authentication in ASP.NET Core
- Introduction to ASP.NET Core Identity
- ASP.NET Core Identity Tutorial From Scratch
- Sending Email Confirmation in ASP.NET Core
- Add Custom Fields to the user in ASP.NET Core Identity
- Change Primary key in ASP.NET Core Identity
- JWT Authentication in ASP.NET Core
- Introduction to Authorization
- Simple Authorization using Authorize attribute
- Adding & Managing Claims in ASP.NET Core Identity
- Claim Based Authorization in ASP.NET Core
- Policy-based Authorization
- Resource-Based Authorization