Commit 73c7e377 authored by Eric Dieckman's avatar Eric Dieckman
Browse files

User info lookup working

parent 86bb3341
Loading
Loading
Loading
Loading
+92 −5
Original line number Diff line number Diff line
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using Azure.Core;
using Azure.Identity;
@@ -12,6 +14,9 @@ using Cals.Visor.Infrastructure;
using Hellang.Middleware.ProblemDetails;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Quartz;
@@ -19,7 +24,7 @@ using Quartz;
#pragma warning disable SA1005
#pragma warning disable SA1512 // Single-line comments should not be followed by blank line

var builder = WebApplication.CreateBuilder(args);
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

ConfigureAzureKeyVault(builder);

@@ -39,6 +44,11 @@ builder.Services
            ValidateIssuer = true
        };

        ConfigurationManager<OpenIdConnectConfiguration> configurationManager = new(
            options.MetadataAddress,
            new OpenIdConnectConfigurationRetriever()
        );

        // Optional event logging for debugging
        options.Events = new JwtBearerEvents
        {
@@ -47,10 +57,86 @@ builder.Services
                Console.WriteLine($"JWT authentication failed: {context.Exception.Message}");
                return Task.CompletedTask;
            },
            OnTokenValidated = context =>
            OnTokenValidated = async context =>
            {
                Console.WriteLine($"JWT token valid for: {context.Principal?.Identity?.Name}");
                return Task.CompletedTask;
                IMemoryCache cache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
                ILogger logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>()
                    .CreateLogger("JwtBearer");

                // Handle both token types
                string? accessToken = context.SecurityToken switch
                {
                    System.IdentityModel.Tokens.Jwt.JwtSecurityToken jwt => jwt.RawData,
                    Microsoft.IdentityModel.JsonWebTokens.JsonWebToken json => json.EncodedToken,
                    _ => null
                };

                if (string.IsNullOrEmpty(accessToken))
                {
                    logger.LogWarning("No access token found in SecurityToken.");
                    return;
                }

                // Check cache first
                if (!cache.TryGetValue(accessToken, out Dictionary<string, object>? userInfo))
                {
                    // Fetch the discovery document
                    OpenIdConnectConfiguration config = await configurationManager.GetConfigurationAsync(context.HttpContext.RequestAborted);
                    string userInfoEndpoint = config.UserInfoEndpoint;

                    if (string.IsNullOrEmpty(userInfoEndpoint))
                    {
                        logger.LogError("UserInfo endpoint not found in discovery document.");
                        context.Fail("UserInfo endpoint unavailable.");
                        return;
                    }


                    try
                    {
                        using var http = new HttpClient();
                        http.DefaultRequestHeaders.Authorization =
                            new AuthenticationHeaderValue("Bearer", accessToken);

                        HttpResponseMessage response = await http.GetAsync(userInfoEndpoint);
                        response.EnsureSuccessStatusCode();

                        Dictionary<string, object>? json = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>();
                        userInfo = json ?? new();

                        // Cache until token expiration (use exp claim if present)
                        string? expClaim = context.Principal?.FindFirst("exp")?.Value;
                        if (!string.IsNullOrEmpty(expClaim) && long.TryParse(expClaim, out long expUnix))
                        {
                            DateTimeOffset expiresAt = DateTimeOffset.FromUnixTimeSeconds(expUnix);
                            cache.Set(accessToken, userInfo, expiresAt);
                        }
                        else
                        {
                            cache.Set(accessToken, userInfo, TimeSpan.FromMinutes(5)); // fallback
                        }

                        logger.LogInformation("Fetched and cached userinfo for token.");
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "Failed to fetch userinfo.");
                        context.Fail("Could not retrieve user info.");
                        return;
                    }
                }

                // Attach claims from userinfo to the identity
                if (userInfo != null)
                {
                    var identity = (ClaimsIdentity)context.Principal!.Identity!;
                    foreach (KeyValuePair<string, object> kvp in userInfo)
                    {
                        identity.AddClaim(new Claim(kvp.Key, kvp.Value?.ToString() ?? ""));
                    }
                }

                await Task.CompletedTask;
            }
        };

@@ -99,7 +185,8 @@ builder.Services.AddAuthorizationBuilder()

builder.Services.AddControllers();

builder.Services.AddInMemoryCaching();
//builder.Services.AddInMemoryCaching();
builder.Services.AddMemoryCache();

builder.Services.AddHttpContextAccessor();