How to Restrict Pages using Middleware and PageFilters
Today's quick tip demonstrates how to limit users to a small number of pages
Restricting pages to a user on a website can be tricky. There are times when users are only allowed to view a select number of pages.
While this is easily implemented through MS Identity using the [Authorize]
attribute on most pages, how could this concept apply to anonymous users?
The most relevant question is why would someone need this?
- Website launches - Creating a landing page as a way to gather emails to notify users when the new site launches; Restrict access to only the landing page and a thank you page after submitting an email.
- Maintenance - During maintenance periods, users aren't able to access other pages. They only see the maintenance page.
Luckily, there's a couple of ways to accomplish this and it's been available since ASP.NET Core 2.0.
Restrict Users With IAsyncPageFilter
One way to accomplish this is with the IAsyncPageFilter
class.
Razor Page Filters are implemented with the IPageFilter
and IAsyncPageFilter
interfaces and they run before and after the Razor Page Handler. They work similar to ActionFilters, but in addition to working on a single page, it can also apply to a larger scale of pages.
In the example below, a list of pages are contained in an array at the top of the class allowing only the Index
and About
page available to anyone accessing the site. If a user tries to access any other page (or even a page that doesn't exist), they're returned to the Index
page.
\Filters\PageAccessFilter.cs
public class PageAccessFilter : IAsyncPageFilter { private readonly string[] _allowedPages = ["/Index", "/About"];
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
PageHandlerExecutionDelegate next) { if (!_allowedPages.Any(p => context.ActionDescriptor.RelativePath.Contains(p))) { context.HttpContext.Response.Redirect("/Index"); return; }
await next(); }
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) => Task.CompletedTask; }
In the Program.cs
file, add the PageAccessFilter
filter through the AddRazorPages()
method.
builder.Services.AddRazorPages(options => { options.Conventions.ConfigureFilter(new PageAccessFilter()); });
While this can be used for either a page or an entire website, another alternative is to implement a Middleware component.
Restrict Users with Middleware
The Middleware version is similar and can be used in a more global manner as opposed to the PageFilter approach. The Middleware approach is a bit more enticing since it's encountered earlier in the pipeline process.
Keep in mind, a Middleware's order of operation is extremely important when adding Middleware components.
Here is the PageAccessMiddleware
component
\Middleware\PageAccessMiddleware.cs
public class PageAccessMiddleware(ILogger<PageAccessMiddleware> logger, RequestDelegate next) { private readonly string[] _allowedPages = ["/Index", "/About"];
public async Task InvokeAsync(HttpContext context) { if (!_allowedPages.Any(p => context.Request.Path.HasValue && context.Request.Path.Value.Contains(p))) { context.Response.Redirect("/Index"); return; }
await next(context); } }
In the Program.cs
, use the UseMiddleware<PageAccessMiddleware>()
method at the top of the method.
app.UseMiddleware<PageAccessMiddleware>();
Let's clean it up a bit by creating a Middleware extension method.
public static class PageAccessMiddlewareExtensions { public static IApplicationBuilder UsePageAccessMiddleware(this IApplicationBuilder builder) => builder.UseMiddleware<PageAccessMiddleware>(); }
Finally, replace the UseMiddleware()
method with the new extension method.
app.UsePageAccessMiddleware();
Once updated, running the example displays the main page and allow access to the About page. If trying to access the Privacy page, it redirects the user to the Index page.
For additional points, instead of hard-coding an array of pages, they could easily be passed into the Middleware component.
Conclusion
In this short post, we covered a way to restrict users to a collection of pages.
Since HttpContext.Request
is available in both versions, other possibilities to implement could include:
- Limiting pages based on a directory
- Storing a list of allowable pages in a database
- Using a configuration file to enable/disable a website (i.e. maintenance purposes)
If a website has to "go down" temporarily or force users to one or more pages, PageFilters or Middleware is a simple, quick, and efficient solution.
How do you restrict users from other pages? What could be added to make the component better? Post your comments below and let's discuss.