Restricting Menu Access
Introduction
I don't want all users to have access to the Email Test and Hangfire Dashboard options from the main menu. Email Test has the useful feature that, although it's called 'Email Test', it has a button that can manually trigger reminder emails, whilst the Hangfire Dashboard is not something that should or needs to be exposed to users.
One way to do this is to assign myself to a 'group' in Azure and then test for that when the menu is loaded, however it struck me that as I was already getting the email address of the logged in user that I could use that instead. This has the advantage that I have the code already and is relatively easy to implement. I will describe this technique in this post, but hope to return to the Azure 'group' method as this probably has wider application and is certainly more flexible.
YouTube Video
Summary of Tasks
- Copy code establishing the email address of the current user from Index.razor and paste into NavMenu.razor
- Amend NavMenu.razor to limit access to 'Email Test' and 'Hangfire Dashboard' to selected email addresses.
- Re-design and rename 'EmailTestPage.razor' to something more appropriate to reflect its use as a manual way of triggering email reminders
- Tidy up by renaming 'Email Test' menu item to 'Manual Emails', or similar.
Copy Code to NavMenu.razor
The first action in the 'OnInitializedAsync' method in Index.razor is to establish the email address of the currently logged in user. Copy the code and paste into a new method 'OnInitializedAsync' in NavMenu.razor. i.e.
protected override async Task OnInitializedAsync()
{
if (authState != null)
{
principal = (await authState).User;
}
foreach (Claim claim in principal.Claims)
{
if(claim.Type == "emails")
{
UserEmail = claim.Value;
}
}
}
We will also need to add the following using statements at the top of NavMenu.razor
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.Identity.Client
@using System
@using System.Collections.Generic
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProviderAnd declare the following variables at the top of the 'code' section:
private string UserEmail;
[CascadingParameter]
private Task<AuthenticationState>? authState { get; set; }
private ClaimsPrincipal? principal;Amend NavMenu.razor to limit access
Now that we have the user's email address we can 'hardcode' access to the Email Test and Hangfire Dashboard menu items by surrounding these menu options by an 'if' statement that tests for selected email addresses. For example:
@if (UserEmail == "MyEmail@MyDomain.uk" || UserEmail == "anotherperson@anotherdomain.com")
{
<div class="nav-item px-3">
<NavLink class="nav-link" href="emailtest">
<span class="oi oi-list-rich" aria-hidden="true"></span> Email Test
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="dashboard">
<span class="oi oi-list-rich" aria-hidden="true"></span> Hangfire Dashboard
</NavLink>
</div>
}In the above example the 'if' statement tests for either "MyEmail@MyDomain.uk" or "anotherperson@anotherdomain.com"; if either is true the menu items will be displayed.
Guilty, as charged
Before moving on, I must admit to two sins in the above. Firstly I have copied code, breaking the 'Do Not Repeat' yourself principle (DRY). It would be much better practice to move the code used to determine the user email address to a separate class and call it from both Index and NavMenu razor pages. Secondly, I have hardcoded the two email addresses I am permitting to access the menu options. If I wanted to add, delete or change an email address I would have to change the C# code, requiring re-publishing the whole application.
Re-Design of EmailTestPage.razor
What started as a test page now has a purpose, to enable an administrator to trigger email reminders manually. We should redesign the page to reflect this.
- Change the @page routing to "/manualreminders/"
- Change the page title to "Manual Birthday Reminder Emails"
- Delete the div describing the settings for common email providers
- Add a div above the button to describe what the button is used for
- Change the button text
The following shows suggested changes for the top of the file.
@page "/manualreminders/"
<PageTitle>Birthday Reminders</PageTitle>
@using MailKit.Net.Smtp;
@using MailKit.Security;
@using MimeKit;
@using MimeKit.Text;
@inject IEmailService EmailService
@inject IPersonService PersonService
<h3>Manual Birthday Reminder Emails</h3>
<hr />
<div>
<p>By clicking the button the system will look for forthcoming
birthdays and will send reminder emails.</p>
</div>
<div class="button-container">
<button type="button" class="e-btn e-normal e-primary"
@onclick="@SendEmail">Send Birthday Reminder Emails</button>
</div>Save and close the file (EmailTestPage.razor at this stage.)
Re-Name menu item
To keep things as consistent as we can, re-name EmailTestPage.razor to, say, 'ManualEmailPage.razor'.
Open NavMenu.razor and make the following changes:
- Change the href to "manualreminders"
- Change the menu item text to "Manual Emails"
One Last Change
At present, the application has an 'About' item in the top bar which would lead the user to a Microsoft ASP.NET documentation page - the very last thing a user would want or need to see! To remove this select 'MainLayout.razor' from the Shared folder and comment out (or remove entirely) the following line:
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>To comment out the line surround it using either of the following techniques:
<!-- Comment goes here -->
@* Comment goes here *@It also occurs to me that 'About' could be replaced by 'Help' which could either redirect to another razor page within the application or alternatively could point to an external web page. In this case, as our application is pretty self-explanatory I would think minimal help could be supplied in one extra razor page within the application.
References
- Code for this post: Restricting Menu Access