Files
Automatic-Parking/Source/ProofOfConcept/Utilities/TeslaOIDCConfigurationManager.cs
Szakáts Alpár Zsolt df60b4cda5
All checks were successful
Build, Push and Run Container / build (push) Successful in 25s
Enhances Tesla OIDC authentication
Improves authentication by adding a signing key resolver and overriding the token endpoint.

This change ensures proper validation of Tesla's OIDC tokens by fetching the signing keys from the issuer's `certs` endpoint and caching them. It also configures the token endpoint required for Tesla authentication.
2025-08-16 22:54:56 +02:00

75 lines
3.0 KiB
C#

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<OpenIdConnectConfiguration>
{
private readonly IConfigurationManager<OpenIdConnectConfiguration> _inner;
private readonly string _tokenEndpointOverride;
// No HttpClient/ServiceProvider needed — uses default retriever internally
public TeslaOIDCConfigurationManager(string metadataAddress, string tokenEndpointOverride)
{
_tokenEndpointOverride = tokenEndpointOverride;
_inner = new ConfigurationManager<OpenIdConnectConfiguration>(
metadataAddress,
new OpenIdConnectConfigurationRetriever());
}
public async Task<OpenIdConnectConfiguration> 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<string, (DateTimeOffset exp, SecurityKey[] keys)> cache;
public SigningKeyResolver(HttpClient backChannel, TimeSpan cacheDuration)
{
this.backChannel = backChannel;
this.cacheDuration = cacheDuration;
this.cache = new ConcurrentDictionary<string,(DateTimeOffset exp, SecurityKey[] keys)>();
}
public IEnumerable<SecurityKey> 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<SecurityKey>();
// 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;
}
}
}