Skip to content

Instantly share code, notes, and snippets.

@vertonghenb
Last active December 5, 2021 00:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vertonghenb/65fe177b04f13037c83e745fefa123a9 to your computer and use it in GitHub Desktop.
Save vertonghenb/65fe177b04f13037c83e745fefa123a9 to your computer and use it in GitHub Desktop.
Blazor Authorization with Auth0
@page "/add-weather"
@using WeatherStation.Shared
@inject HttpClient Http
@inject NavigationManager NavigationManager
@attribute [Authorize(Roles ="Administrator")]
<PageTitle>Add Weather Data</PageTitle>
<h3>Add Weather Data</h3>
<EditForm Model="forecast" OnValidSubmit="Add">
<div class="form-group">
<label>Date</label>
<InputDate class="form-control" @bind-Value="forecast.Date" />
</div>
<div class="form-group">
<label>Temperature (C)</label>
<InputNumber class="form-control" @bind-Value="forecast.TemperatureC" />
</div>
<div class="form-group">
<label>Summary</label>
<InputSelect class="form-control" @bind-Value="forecast.Summary">
<option value="">Select</option>
<option value="Freezing">Freezing</option>
<option value="Bracing">Bracing</option>
<option value="Chilly">Chilly</option>
<option value="Cool">Cool</option>
<option value="Mild">Mild</option>
<option value="Warm">Warm</option>
<option value="Balmy">Balmy</option>
<option value="Hot">Hot</option>
<option value="Sweltering">Sweltering</option>
<option value="Scorching">Scorching</option>
</InputSelect>
</div>
<button class="btn btn-primary mt-2" type="submit">Add Weather</button>
</EditForm>
@code {
private WeatherForecast forecast = new();
protected override void OnInitialized()
{
base.OnInitialized();
forecast.Date = DateTime.Now;
}
private async Task Add()
{
var response = await Http.PostAsJsonAsync<WeatherForecast>("WeatherForecast",forecast);
response.EnsureSuccessStatusCode();
NavigationManager.NavigateTo("fetchdata");
}
}
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using System.Security.Claims;
using System.Text.Json;
namespace WeatherStation.Client.Shared;
public class ArrayClaimsPrincipalFactory<TAccount> : AccountClaimsPrincipalFactory<TAccount> where TAccount : RemoteUserAccount
{
public ArrayClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
: base(accessor)
{ }
// when a user belongs to multiple roles, Auth0 returns a single claim with a serialised array of values
// this class improves the original factory by deserializing the claims in the correct way
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(TAccount account, RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
var claimsIdentity = (ClaimsIdentity)user.Identity;
if (account != null)
{
foreach (var kvp in account.AdditionalProperties)
{
var name = kvp.Key;
var value = kvp.Value;
if (value != null &&
value is JsonElement element && element.ValueKind == JsonValueKind.Array)
{
claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(kvp.Key));
var claims = element.EnumerateArray()
.Select(x => new Claim(kvp.Key, x.ToString()));
claimsIdentity.AddClaims(claims);
}
}
}
return user;
}
}
@page "/"
<PageTitle>Index</PageTitle>
<AuthorizeView>
<Authorized>
@foreach (var claim in context.User.Claims)
{
<p>@claim.Type - @claim.Value</p>
}
</Authorized>
<NotAuthorized>
Please login to see all the claims, <a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
<AuthorizeView Roles="Administrator">
Only Administrators can see this.
</AuthorizeView>
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">WeatherStation</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</div>
// 👇
<AuthorizeView Roles="Administrator">
<div class="nav-item px-3">
<NavLink class="nav-link" href="add-weather">
<span class="oi oi-list-rich" aria-hidden="true"></span> Add Weather
</NavLink>
</div>
</AuthorizeView>
// 👆
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
/**
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
const claimName = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role'
if (event.authorization) {
api.idToken.setCustomClaim(claimName, event.authorization.roles);
api.accessToken.setCustomClaim(claimName, event.authorization.roles);
}
}
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using WeatherStation.Client;
using WeatherStation.Client.Shared;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient("WeatherAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("WeatherAPI"));
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Auth0", options.ProviderOptions);
options.ProviderOptions.ResponseType = "code";
options.ProviderOptions.AdditionalProviderParameters.Add("audience", builder.Configuration["Auth0:Audience"]);
}).AddAccountClaimsPrincipalFactory<ArrayClaimsPrincipalFactory<RemoteUserAccount>>(); // 👈
await builder.Build().RunAsync();
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WeatherStation.Shared;
namespace WeatherStation.Server.Controllers;
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private static readonly List<WeatherForecast> forecasts = new();
static WeatherForecastController()
{
forecasts.AddRange(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}));
}
[HttpGet]
public IEnumerable<WeatherForecast> GetForecasts()
{
return forecasts;
}
[HttpPost]
[Authorize(Roles = "Administrator")] // 👈
public WeatherForecast CreateForecast(WeatherForecast forecast)
{
forecasts.Add(forecast);
return forecast;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment