From df60b4cda51dbb77426ef1a3e4942b2efebd53aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szak=C3=A1ts=20Alp=C3=A1r=20Zsolt?= Date: Sat, 16 Aug 2025 22:54:19 +0200 Subject: [PATCH] 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. --- Source/ProofOfConcept/Program.cs | 10 +++-- .../TeslaOIDCConfigurationManager.cs | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Source/ProofOfConcept/Program.cs b/Source/ProofOfConcept/Program.cs index 18bf0c0..52d9c71 100644 --- a/Source/ProofOfConcept/Program.cs +++ b/Source/ProofOfConcept/Program.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; using ProofOfConcept.Models; using ProofOfConcept.Services; using ProofOfConcept.Utilities; @@ -55,9 +56,6 @@ builder.Services.AddAuthentication(o => // This must match exactly what you register at Tesla o.CallbackPath = new PathString("/token-exchange"); - - o.TokenValidationParameters.ValidateIssuer = false; - o.TokenValidationParameters.ValidIssuers = ["https://fleet-auth.tesla.com/oauth2/v3/nts", "https://auth.tesla.com/oauth2/v3", "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/nts"]; // Scopes you actually need o.Scope.Clear(); @@ -74,6 +72,12 @@ builder.Services.AddAuthentication(o => // If keys rotate during runtime, auto-refresh JWKS o.RefreshOnIssuerKeyNotFound = true; + // Set token validation parameters + o.TokenValidationParameters.ValidIssuers = ["https://fleet-auth.tesla.com/oauth2/v3/nts", "https://auth.tesla.com/oauth2/v3", "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/nts"]; + + var signingKeyResolver = new TeslaOIDCConfigurationManager.SigningKeyResolver(o.Backchannel, TimeSpan.FromHours(12)); + o.TokenValidationParameters.IssuerSigningKeyResolver = signingKeyResolver.Resolve; + // Add Tesla's required audience to the token request o.Events = new OpenIdConnectEvents { diff --git a/Source/ProofOfConcept/Utilities/TeslaOIDCConfigurationManager.cs b/Source/ProofOfConcept/Utilities/TeslaOIDCConfigurationManager.cs index 4c3f4c2..b02ef6f 100644 --- a/Source/ProofOfConcept/Utilities/TeslaOIDCConfigurationManager.cs +++ b/Source/ProofOfConcept/Utilities/TeslaOIDCConfigurationManager.cs @@ -1,5 +1,8 @@ +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; @@ -28,4 +31,45 @@ public sealed class TeslaOIDCConfigurationManager : IConfigurationManager _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; + } + } } \ No newline at end of file