using System.Net.Http.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Options; namespace SIFinance.Shared; // Jeton Graph "app-only" (client_credentials) pour les appels serveur-à-serveur // (synchro du groupe Entra ID, envoi d'emails de notification, etc.). Mis en cache 55 // minutes pour éviter un aller-retour Azure AD à chaque appel. Vit dans SIFinance.Shared // (et non SIFinance.Api) car les modules en ont besoin eux aussi (ex. notification par // email à la création d'une demande), et référencer SIFinance.Api depuis un module // créerait un cycle. public class EntraGraphTokenProvider(IHttpClientFactory httpClientFactory, IOptions options) { private string? _token; private DateTime _expiresAt; public async Task GetTokenAsync(CancellationToken ct) { if (_token is not null && DateTime.UtcNow < _expiresAt) return _token; var opts = options.Value; var http = httpClientFactory.CreateClient(); var response = await http.PostAsync( $"https://login.microsoftonline.com/{opts.TenantId}/oauth2/v2.0/token", new FormUrlEncodedContent(new Dictionary { ["grant_type"] = "client_credentials", ["client_id"] = opts.ClientId, ["client_secret"] = opts.ClientSecret, ["scope"] = "https://graph.microsoft.com/.default", }), ct); response.EnsureSuccessStatusCode(); var payload = await response.Content.ReadFromJsonAsync(cancellationToken: ct) ?? throw new InvalidOperationException("Réponse de jeton Graph vide."); _token = payload.AccessToken; _expiresAt = DateTime.UtcNow.AddMinutes(55); return _token; } private class TokenResponse { [JsonPropertyName("access_token")] public string AccessToken { get; set; } = ""; } }