using System.Security.Claims; using Fido2NetLib; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using Passwordless; // TODO: RoslynPad code for key generation var jwk = JsonWebKey.Create(File.ReadAllText("./demo-jwk.json")); //var host = "http://localhost:5172"; var host = "https://demo.larcanum.net"; var builder = WebApplication.CreateBuilder(args); // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddFido2(options => { // server domain MUST match the actual domain name that the client uses to make the request from options.ServerDomain = host.Substring(host.LastIndexOf("/", StringComparison.Ordinal) + 1); options.ServerName = "FIDO2 Test"; options.Origins = [host]; options.TimestampDriftTolerance = 300000; }); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Events = new JwtBearerEvents() { OnAuthenticationFailed = ctx => { Console.WriteLine(ctx.Exception); return Task.CompletedTask; }, OnTokenValidated = ctx => { Console.WriteLine($"Valid token from {ctx.SecurityToken.Issuer}"); return Task.CompletedTask; } }; options.RequireHttpsMetadata = false; // dev only!!! options.Authority = host; options.TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = jwk, ValidIssuer = host, ValidAudience = host, NameClaimType = ClaimTypes.NameIdentifier, // important to get the "sub" claim mapped to User.Identity.Name RoleClaimType = ClaimTypes.Role, }; }); builder.Services.AddAuthorization(authorizationOptions => { authorizationOptions.AddPolicy("ProtectedPolicy", policyBuilder => policyBuilder .RequireClaim("permissions", "MagicClaim") .RequireRole("grunt")); }); builder.Services.AddMemoryCache(); builder.Services.Configure(config => { config.Key = jwk; config.Host = host; }); builder.Services.AddTransient(); builder.Services.AddTransient(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseDefaultFiles(); app.UseStaticFiles(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapGet("/buildCredentialOptions", ([FromQuery] string login, CredentialManager credMan) => credMan.BuildCredentialOptions(login)) .WithName("BuildCredentialOptions") .WithOpenApi(); app.MapPost("/registerCredential", async ([FromQuery] string login, [FromBody] AuthenticatorAttestationRawResponse attestationResponse, CredentialManager credMan) => await credMan.RegisterCredential(login, attestationResponse)) .WithName("RegisterCredential") .WithOpenApi(); app.MapGet("/buildAssertionOptions", async ([FromQuery] string login, CredentialManager credMan) => await credMan.BuildAssertionOptions(login)) .WithName("BuildAssertionOptions") .WithOpenApi(); app.MapPost("/verifyCredential", async ([FromBody] AuthenticatorAssertionRawResponse assertionResponse, CredentialManager credMan) => Results.Json(await credMan.VerifyCredential(assertionResponse))) .WithName("VerifyCredential") .WithOpenApi(); app .MapGet("/protected", (HttpContext context) => { var data = new Dictionary { ["Status"] = "Success!", ["UserName"] = context.User?.Identity?.Name ?? "", ["Permissions"] = context.User?.Claims.Where(c => c.Type == "permissions").Select(c => c.Value) ?? Enumerable.Empty(), ["IsAdmin"] = context.User?.IsInRole("admin"), ["IsGrunt"] = context.User?.IsInRole("grunt"), ["IsNoob"] = context.User?.IsInRole("noob"), }; return data; }) .WithName("Protected") .RequireAuthorization("ProtectedPolicy"); // .RequireAuthorization(policy => // { // policy.RequireAuthenticatedUser(); // }); app.Run();