using System.Collections.Concurrent; using System.IdentityModel.Tokens.Jwt; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; namespace ProofOfConcept.Utilities; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; public sealed class TeslaOIDCConfigurationManager : IConfigurationManager { private readonly IConfigurationManager _inner; private readonly string _tokenEndpointOverride; // No HttpClient/ServiceProvider needed — uses default retriever internally public TeslaOIDCConfigurationManager(string metadataAddress, string tokenEndpointOverride) { _tokenEndpointOverride = tokenEndpointOverride; _inner = new ConfigurationManager( metadataAddress, new OpenIdConnectConfigurationRetriever()); } public async Task GetConfigurationAsync(CancellationToken cancel) { var cfg = await _inner.GetConfigurationAsync(cancel).ConfigureAwait(false); cfg.TokenEndpoint = _tokenEndpointOverride; // <-- required by Tesla return cfg; } public void RequestRefresh() => _inner.RequestRefresh(); public sealed class SigningKeyResolver { private readonly HttpClient backChannel; private readonly TimeSpan cacheDuration; private readonly ConcurrentDictionary cache; public SigningKeyResolver(HttpClient backChannel, TimeSpan cacheDuration) { this.backChannel = backChannel; this.cacheDuration = cacheDuration; this.cache = new ConcurrentDictionary(); } public IEnumerable Resolve(string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) { JwtSecurityToken jwt = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(token); string? issuer = jwt.Issuer?.TrimEnd('/'); // Issuer is empty if (string.IsNullOrEmpty(issuer)) return Array.Empty(); // Serve from cache if fresh if (cache.TryGetValue(issuer, out var entry) && entry.exp > DateTimeOffset.UtcNow) return entry.keys; // Fetch JWKS from the same issuer (sync-over-async kept local to this callback) string jwksUrl = $"{issuer}/certs"; string json = backChannel.GetStringAsync(jwksUrl).GetAwaiter().GetResult(); // Get result JsonWebKeySet jwks = new JsonWebKeySet(json); SecurityKey[] keys = jwks.Keys.Select(k => (SecurityKey)k).ToArray(); // Cache cache[issuer] = (DateTimeOffset.UtcNow.Add(this.cacheDuration), keys); return keys; } } }