ASP.NET Core Middleware
Aserto Authorization middleware for ASP.NET core.
Overview
This package allows ASP.NET applications to use Aserto as the Authorization provider.
Installation
Aserto.AspNetCore.Middleware is provided as a NuGet package.
It can be installed:
- Using Package Manager:
Install-Package Aserto.AspNetCore.Middleware
- Using .NET CLI
dotnet add package Aserto.AspNetCore.Middleware
Configuration
The following configuration settings are required for Aserto.AspNetCore middleware to run with Topaz. You can add them to your appsettings.json
:
// appsettings.json
"Aserto": {
"ServiceUrl": "https://localhost:8282"
"PolicyName": "YOUR_POLICY_NAME",
"PolicyRoot": "YOUR_POLICY_ROOT"
}
These settings can be retrieved from the Policy Settings page of your Aserto account.
The middleware accepts the following optional parameters:
Parameter name | Default value | Description |
---|---|---|
Enabled | true | Enables or disables Aserto Authorization. |
ServiceUrl | https://authorizer.prod.aserto.com:8443 | Sets the URL for the authorizer endpoint. |
Decision | "allowed" | The decision that will be used by the middleware when creating an authorizer request. |
Identity
To determine the identity of the user, the middleware checks the following Claim types:
Name | Description | URI |
---|---|---|
E-Mail Address | The e-mail address of the user | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress |
Name | The unique name of the user | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name |
Name Identifier | The SAML name identifier of the user | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier |
These can be overwritten by passing other Claim types to the AsertoDecisionRequirement
:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//..
services.AddAuthorization(options =>
{
options.AddPolicy("Aserto", policy =>
policy.Requirements.Add(new AsertoDecisionRequirement(new List<string>
{
"mytype1",
"mytype2"
})));
});
//..
}
URL path to policy mapping
By default, when computing the policy path, the middleware:
- converts all slashes to dots
- converts any character that is not alpha, digit, dot or underscore to underscore
- converts uppercase characters in the URL path to lowercases
This behavior can be overwritten by providing a custom function to the PolicyPathMapper
AsertoAuthorization option:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//..
// Adds the Aserto Authorization service
services.AddAsertoAuthorization(options =>
{
Configuration.GetSection("Aserto").Bind(options);
options.PolicyPathMapper = (policyRoot, httpRequest) =>
{
return "custom.policy.path";
};
});
//..
}
Topaz Authorization on a ASP.NET Core MVC application
Creating and setting up the project
In this quickstart we want to add API access support using Topaz Authorization using Auth0 Authentication.
Setting up the ASP.NET Core application
First create a directory for the application, then use a template to create a ASP.NET application:
md dotnetmvc
cd dotnetmvc
md src
cd src
dotnet new mvc -n QuickstartMVC
This will create a .NET MVC application that exposes the following endpoints and http methods:
GET
on "/"GET
on "/Home/Privacy"
You can run the app using the following command:
dotnet run --project dotnetmvc/src/QuickstartMVC/QuickstartMVC.csproj
Adding authentication
This guide assumes that you already have an Auth0 account set up and an Auth0 Regular Web Application created.
For authentication we will use the Cookie and OpenID Connect(OIDC) authentication middleware. For this you need to add the Microsoft.AspNetCore.Authentication.OpenIdConnect
package to your application. This can be done using NuGet by running the following command in your project directory:
dotnetmvc\src\QuickstartMVC> dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
To enable authentication, you will need to update the ConfigureServices
in your Startup
class and:
- Configure Cookie for HTTPS
- Make a call to the
AddAuthentication
method and set the:DefaultAuthenticateScheme
toCookieAuthenticationDefaults.AuthenticationScheme
DefaultSignInScheme
toCookieAuthenticationDefaults.AuthenticationScheme
DefaultChallengeScheme
toCookieAuthenticationDefaults.AuthenticationScheme
- Add the
CookieExtension
- Configure the OIDC authentication handler by adding a call to
AddOpenIdConnect
and setting the:Authority
to the Auth0 DomainClientId
your Auth0 Client IDClientSecret
to your Auth0 Client SecretResponseType
toOpenIdConnectResponseType.Code
ClaimsIssuer
to "Auth0"
- Handle the logout redirection
Below you've got a code sample that enables OIDC authentication:
// Startup.cs
//..
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
//..
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Cookie configuration for HTTPS
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Add authentication services
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options => {
// Set the authority to your Auth0 domain
var domain = "YOUR_AUTH0_DOMAIN";
options.Authority = $"https://{domain}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = "YOUR_AUTH0_CLIENT_ID";
options.ClientSecret = "YOUR_AUTH0_CLIENT_SECRET";
// Set response type to code
options.ResponseType = OpenIdConnectResponseType.Code;
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
// Set the callback path, so Auth0 will call back to http://localhost:5001/callback
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/callback");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
options.Events = new OpenIdConnectEvents
{
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{domain}/v2/logout?client_id={options.ClientId}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
});
services.AddControllersWithViews();
}
//..
To add the authentication middleware to the middleware pipeline, add a call to the UseAuthentication
method in your Startup's Configure
method:
// Startup.cs
//..
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// Adds the Authentication middleware to the middleware pipeline
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
//..
To handle authentication requests, we're going to add a new controller that can manage login and logout requests. This new controller is going to be named AccountController and needs to be added under the Controller folder:
// Controllers/AccountController.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
public class AccountController : Controller
{
public async Task Login(string returnUrl = "/")
{
await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl });
}
public IActionResult AccessDenied()
{
return View();
}
public async Task Logout()
{
await HttpContext.SignOutAsync("Auth0", new AuthenticationProperties
{
// Indicate here where Auth0 should redirect the user after a logout.
// Note that the resulting absolute Uri must be added to the
// **Allowed Logout URLs** settings for the app.
RedirectUri = Url.Action("Index", "Home")
});
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
Next we're going to add the login and logout buttons to the shared layout. To do this, you need to edit the Views/Shared/_Layout.cshtml and add the following lines:
@* Views/Shared/_Layout.cshtml *@
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
@* ... Code omitted for brevity ... *@
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="nav navbar-nav navbar-right">
@if (User.Identity.IsAuthenticated)
{
<li><a id="qsLogoutBtn" asp-controller="Account" asp-action="Logout">Logout</a></li>
}
else
{
<li><a id="qsLoginBtn" asp-controller="Account" asp-action="Login">Login</a></li>
}
</ul>
</div>
</div>
</nav>
</header>
</body>
@* ... Code omitted for brevity ... *@
The callback url(https://localhost:5001/callback) configured in the ConfigureServices
method of Startup.cs
needs to be set as an Allowed Callback URL in Auth0. Similarly, the logout url(https://localhost:5001/) needs to be set in the Allowed Logout URLs list.
Creating policy for QuickstartMVC
To add authentication to your backend application, first you need to create an Aserto policy that contains the authentication rules for the APIs provided by the application.
For the QuickstartMVC
application created earlier, the policy folder structure should look like this:
.
├── Makefile
├── README.md
└── src
├── .manifest
└── quickstartmvc
├── account
│ ├── login
│ │ └── get.rego
│ └── logout
│ └── get.rego
├── get.rego
└── home
├── privacy
│ └── get.rego
└── profile
└── get.rego
Where the ./src/.manifest
file contains our policy roots:
{
"roots": ["quickstartmvc"]
}
And the ./src/quickstartmvc/profile/get.rego
policy file contains rules for the GET
http method on /profile
URL path:
package quickstartmvc.GET.home.profile
default allowed = false
allowed {
caller = input.user
caller.identities[i].verified == true
}
This policy allows access to /profile
on GET
only to users that have their identities verified.
On the remaining endpoints we're going to allow anonymous access, so the rest of the *.rego files will contain:
allowed = true
If you've created the policy from the default template, you need to create a tag in order to push it to Aserto.
Adding Topaz authorization To enable Topaz authorization, you need to add a dependency to the Aserto dotnet middleware:
$dotnetmvc/src/QuickstartMVC$ dotnet add package Aserto.AspNetCore.Middleware
Configure the Policy root in the appsettings.json
:
"Topaz": {
"ServiceUrl": "https://localhost:8282",
"PolicyName": "YOUR_POLICY_NAME",
}
Configure the Aserto Authorization Service and add an authorization policy in the ConfigureServices method of Startup.cs
:
// Startup.cs
//..
using Microsoft.AspNetCore.Authorization;
using Aserto.AspNetCore.Middleware.Policies;
using Aserto.AspNetCore.Middleware.Extensions;
//..
public void ConfigureServices(IServiceCollection services)
{
//.. Code omitted for brevity
// Adds the Aserto Authorization service
services.AddAsertoAuthorization(options => Configuration.GetSection("Topaz").Bind(options));
// Adds the Aserto policy and configures it as the default Authorization policy
services.AddAuthorization(options =>
{
// User is authenticated via a cookie
var policy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme);
policy.AddRequirements(new AsertoDecisionRequirement());
options.DefaultPolicy = policy.Build();
});
services.AddControllersWithViews();
}
//..
Add the Authorization middleware to the middleware pipeline by adding a call to the UseAuthorization
method in your Startup's Configure
method:
// Startup.cs
//..
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//.. Code omitted for brevity
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
//..
Next you need to create the Profile view that will display the claims of a logged in user. To do this, add a new file Profile.cshtml
under the Views/Home/ folder:
@* Views/Home/Profile.cshtml *@
@using Microsoft.AspNetCore.Authentication
@{
ViewData["Title"] = "Profile";
}
<h1>@ViewData["Title"]</h1>
<p>User profile</p>
@if (Context.User.Identity.IsAuthenticated)
{
<h2>Claims</h2>
<dl>
@foreach (var claim in User.Claims)
{
<dt>@claim.Type</dt>
<dd>@claim.Value</dd>
}
</dl>
<h2>Properties</h2>
<dl>
@foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items)
{
<dt>@prop.Key</dt>
<dd>@prop.Value</dd>
}
</dl>
}
else
{
<h2>User not authenticated.</h2>
}
Add a new navigation bar that will call the view in Views/Shared/_Layout.cshtml:
@* Views/Shared/_Layout.cshtml *@
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
@* ... Code omitted for brevity ... *@
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Profile">Profile</a>
</li>
}
</div>
@* ... Code omitted for brevity ... *@
</div>
</nav>
</header>
</body>
@* ... Code omitted for brevity ... *@
And finally, add the Profile
action in the HomeController.cs:
// Controllers/HomeController.cs
//..
using Microsoft.AspNetCore.Authorization;
//..
[Authorize]
public IActionResult Profile()
{
return View();
}
//..