Securing ASP.NET core (Razor) pages with Auth0 Authentication SDK
Securing ASP.NET core (Razor) pages
Razor Pages is a newer, simplified web application programming model in ASP.NET Core. It removes much of the ceremony of ASP.NET MVC. It is adopting a file-based routing approach. Each Razor Pages file - found under the Pages directory - equates to an endpoint. This post I will demonstrate how to add authentication to Razor Pages using Auth0 ASP.NET Core Authentication SDK
Auth0: sign up
You can sign up for an Auth0's account via: https://auth0.com/signup
After you have created your account you must register your ASP.NET Core application with Auth0.
Create (Auth0) application
Once authenticated and in the dashbord, move to the Applications section and follow the steps:
- Click on Create Application.
- Provide a friendly name for your application (for example, ACME Web App)
- Choose Regular Web Applications as the application type.
- Finally, click the Create button.
You will need some details about the application you just created to communicate with Auth0. You need the Auth0 domain and Client ID. Get these details from the Application Settings section in the Auth0 dashboard.
Configure Callback URLs
The Callback URL of your application is the URL where Auth0 will redirect to after the user has authenticated.
You must add this URL to the list of Allowed URLs for your application in your Application Settings. The URL will mostly take the format: https://YOUR_APPLICATION_URL/callback
Assign the value https://localhost:44339/callback to the Allowed Callback URLs field. Second, add the value https://localhost:44339/ to the Allowed Logout URLs field. Please take note that you must change the address and port to the appropriate port of your environment.
The first value - callback - tells Auth0 which URL to call back after the user authentication. The second value tells Auth0 which URL a user should be redirected to after their logout.
Click the Save Changes button to apply them.
Install dependencies
To integrate Auth0 with your ASP.NET Core must install the Auth0 SDK by installing the Auth0.AspNetCore.Authentication Nuget package to your application.
Install-Package Auth0.AspNetCore.Authentication
Application configuration
Now, open the appsettings.json configuration file, and add the Auth0 section with the following:
"Auth0": {
"Domain": "YOUR_DOMAIN",
"ClientId": "YOUR_CLIENT_ID"
}
These are required fields to ensure the SDK knows which Auth0 tenant and application it should use.
To enable authentication in your ASP.NET Core application, use the middleware provided by Auth0 SDK.
Go to the ConfigureServices method of your Startup class Program.cs and call services.AddAuth0WebAppAuthentication() to configure the Auth0 ASP.NET Core SDK.
using Auth0.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuth0WebAppAuthentication(options => {
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
});
You added a reference to the Auth0.AspNetCore.Authentication namespace at the beginning of the file.
Then you had to add code to invoked the AddAuth0WebAppAuthentication() method with the Auth0 domain and client id as arguments.
app.UseAuthentication();
app.UseAuthorization();
Finally, you add the UseAuthentication() method to enable the authentication middleware. Make sure to call UseAuthentication() before UseAuthorization().
Implementing login
Create an folder named Account within the Pages folder and create a Razor Page named Login.
Open the Login.cshtml.cs file and replace its content with the following:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Authentication;
using Auth0.AspNetCore.Authentication;
namespace contoso.Pages;
public class LoginModel : PageModel
{
public async Task OnGet(string returnUrl = "/")
{
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
// The returnUrl indicates here where Auth0 should redirect the user after a succesfull login.
// Note that the resulting absolute Uri must be added to the
// **Allowed Callback URLs** settings for the app.
.WithRedirectUri(returnUrl)
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
}
}
Above you see the call to ChallengeAsync and it passes the "Auth0" (Auth0Constants.AuthenticationScheme) as the authentication scheme.
This will invoke the OIDC authentication handler that the Auth0 SDK registers internally.
After the OIDC middleware signs the user in, the user is also automatically signed in to the cookie middleware.
Enfore login
The class that is just created only defines the page model for the login page.
The page itself does not render anything. It just creates a set of authentication properties required for the login and triggers the authentication process via Auth0 SDK.
To instruct user to login the UI must be changed. So, open the _Layout.cshtml file under the Pages/Shared folder and update its content as follows:
@if (User.Identity.IsAuthenticated)
{
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Admin">Admin</a>
</li>
</ul>
} else {
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="Account/Login">Login</a>
</li>
</ul>
}
As you probably noticed the if and else statements have been added around the nav items. In the If statement there is a check on the User.Identity.IsAuthenticated property. This property lets you know if the current user is authenticated or not. If the user is not authenticated, a Login link will be shown on the right side of the navigation bar.
Protecting Pages
Although you must authenticate to see the protected pages in the navigation bar, the pages themselves are not protected. You can verify this by going directly to the https://localhost:7204/Admin/ address. You only need to restrict access to the restricted pages to authenticated users. To do this, edit the Program.cs file in the root of the project as shown below:
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Admin");
});
Razor Pages has Authorization Conventions to protect the admin page from unauthorized access. With the approach shown above, it is possible to centralize access control in for the Razor Pages in the application. To learn more about Razor Pages' authorization conventions, check out the official documentation.
Implement logout
To add Logout, you need to sign the user out of both the Auth0 middleware as well as the cookie middleware.
Within the Pages folder create a Razor Page named Logout.
Open the Logout.cshtml.cs file and update its content with the following:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Auth0.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace contoso.Pages
{
public class LogoutModel : PageModel
{
public async Task OnGet()
{
var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
.WithRedirectUri("/")
.Build();
await HttpContext.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
The next step is to make the logout link available to the user.
Open the page _Layout.cshtml file under Pages/Shared folder as follows:
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
@if (User.Identity.IsAuthenticated)
{
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Admin">Admin</a>
</li>
</ul>
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Account/Logout">Logout</a>
</li>
</ul>
}
This will add the logout link on the right side of the navigation bar when the user is authenticated. That link points to the logout page in the Account folder. Add the logout page as a protected page. Open the Program.cs file and update it as shown below:
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Admin");
options.Conventions.AuthorizePage("/Account/Logout");
});
Adding Role-based authorization
Before you can add Role Based Access Control, you will need to ensure the required roles are created and assigned to the corresponding user(s). Follow the guidance explained in assign-roles-to-users to ensure your user gets assigned the admin role.
After the role is created and assigned to the required user(s), a rule must be created that adds the role(s) to the ID token so that it is available in the backend. To do this, go to the new rules page and create an empty rule. Then use the following code for your rule:
function (user, context, callback) {
const assignedRoles = (context.authorization || {}).roles;
const idTokenClaims = context.idToken || {};
idTokenClaims['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'] = assignedRoles;
context.idToken = idTokenClaims;
callback(null, user, context);
}
Integrate roles in your ASP.NET application
You can use the Role Principal method to make sure that only the users with specific roles can access certain actions.
public IActionResult OnGet()
{
if (!User.IsInRole("Admin"))
{
// Do nothing or something :).
}
}