using System.Security.Claims; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; var builder = WebApplication.CreateBuilder(args); if (!builder.Environment.IsDevelopment()) throw new InvalidOperationException("LocalLab accepts untrusted classroom headers and is Development-only"); builder.Services.AddAuthentication("LocalLab").AddScheme("LocalLab", _ => { }); builder.Services.AddAuthorization(options => options.AddPolicy("edit", policy => policy.RequireRole("Editor"))); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/profile", (ClaimsPrincipal user) => new { name = user.Identity!.Name }).RequireAuthorization(); app.MapGet("/edit", () => TypedResults.Ok(new { allowed = true })).RequireAuthorization("edit"); app.Run(); public sealed class LocalLabHandler : AuthenticationHandler { public LocalLabHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) { } protected override Task HandleAuthenticateAsync() { string name = Request.Headers["X-Lab-User"].ToString(); if (name != "reader" && name != "editor") return Task.FromResult(AuthenticateResult.NoResult()); var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, name), new Claim(ClaimTypes.Role, name == "editor" ? "Editor" : "Reader") }, Scheme.Name); return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(identity), Scheme.Name))); } }