49 lines
1.9 KiB
C#
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; } = "";
|
|
}
|
|
}
|