Topaz SDKs - ASP.NET Core middleware - MVC app
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 Authorizer API Key, Tenant ID, Policy root and Policy ID in the appsettings.json
:
"Topaz": {
"AuthorizerApiKey": "YOUR_AUTHORIZER_API_KEY",
"TenantID": "YOUR_ASERTO_TENANT_ID",
"PolicyName": "YOUR_POLICY_NAME",
"PolicyInstanceLabel": "YOUR_POLICY_INSTANCE_ID"
}
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();
}
//..