Files
SIFINANCE/SIFinance.Shared/EntraGraphTokenProvider.cs
2026-09-11 14:55:41 +02:00

49 lines
1.9 KiB
C#

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<AzureAdOptions> options)
{
private string? _token;
private DateTime _expiresAt;
public async Task<string> 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<string, string>
{
["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<TokenResponse>(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; } = "";
}
}