Skip to main content

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 nameDefault valueDescription
EnabledtrueEnables or disables Aserto Authorization.
ServiceUrlhttps://authorizer.prod.aserto.com:8443Sets 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:

NameDescriptionURI
E-Mail AddressThe e-mail address of the userhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
NameThe unique name of the userhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
Name IdentifierThe SAML name identifier of the userhttp://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

note

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:

  1. Configure Cookie for HTTPS
  2. Make a call to the AddAuthentication method and set the:
    • DefaultAuthenticateScheme to CookieAuthenticationDefaults.AuthenticationScheme
    • DefaultSignInScheme to CookieAuthenticationDefaults.AuthenticationScheme
    • DefaultChallengeScheme to CookieAuthenticationDefaults.AuthenticationScheme
  3. Add the CookieExtension
  4. Configure the OIDC authentication handler by adding a call to AddOpenIdConnect and setting the:
    • Authority to the Auth0 Domain
    • ClientId your Auth0 Client ID
    • ClientSecret to your Auth0 Client Secret
    • ResponseType to OpenIdConnectResponseType.Code
    • ClaimsIssuer to "Auth0"
  5. 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 ... *@
note

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
note

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();
}

//..