Enhances Tesla OIDC authentication
All checks were successful
Build, Push and Run Container / build (push) Successful in 25s
All checks were successful
Build, Push and Run Container / build (push) Successful in 25s
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.
This commit is contained in:
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using ProofOfConcept.Models;
|
using ProofOfConcept.Models;
|
||||||
using ProofOfConcept.Services;
|
using ProofOfConcept.Services;
|
||||||
using ProofOfConcept.Utilities;
|
using ProofOfConcept.Utilities;
|
||||||
@@ -55,9 +56,6 @@ builder.Services.AddAuthentication(o =>
|
|||||||
|
|
||||||
// This must match exactly what you register at Tesla
|
// This must match exactly what you register at Tesla
|
||||||
o.CallbackPath = new PathString("/token-exchange");
|
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
|
// Scopes you actually need
|
||||||
o.Scope.Clear();
|
o.Scope.Clear();
|
||||||
@@ -74,6 +72,12 @@ builder.Services.AddAuthentication(o =>
|
|||||||
// If keys rotate during runtime, auto-refresh JWKS
|
// If keys rotate during runtime, auto-refresh JWKS
|
||||||
o.RefreshOnIssuerKeyNotFound = true;
|
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
|
// Add Tesla's required audience to the token request
|
||||||
o.Events = new OpenIdConnectEvents
|
o.Events = new OpenIdConnectEvents
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using Microsoft.IdentityModel.Protocols;
|
using Microsoft.IdentityModel.Protocols;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace ProofOfConcept.Utilities;
|
namespace ProofOfConcept.Utilities;
|
||||||
|
|
||||||
@@ -28,4 +31,45 @@ public sealed class TeslaOIDCConfigurationManager : IConfigurationManager<OpenId
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void RequestRefresh() => _inner.RequestRefresh();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user