Pular para o conteúdo principal

Obter e reaproveitar o token

O token vale 30 minutos e não tem refresh token. O cliente abaixo guarda o token em memória, pede um novo 60 s antes de vencer e descarta o guardado ao receber 401. As regras estão em Acesso.

As credenciais vêm de variáveis de ambiente ou de um cofre de segredos, nunca do código-fonte.

using System.Net.Http.Json;
using System.Text.Json.Serialization;

public sealed class EtechTokenProvider(HttpClient http, string apiKey, string clientId, string clientSecret)
{
static readonly TimeSpan Margem = TimeSpan.FromSeconds(60);
readonly SemaphoreSlim _trava = new(1, 1);
string? _token;
DateTimeOffset _venceEm;

public async Task<string> ObterAsync(CancellationToken ct = default)
{
if (_token is not null && DateTimeOffset.UtcNow < _venceEm - Margem)
return _token;

await _trava.WaitAsync(ct);
try
{
if (_token is not null && DateTimeOffset.UtcNow < _venceEm - Margem)
return _token;

using var pedido = new HttpRequestMessage(HttpMethod.Post, "/oauth2/token")
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = clientId,
["client_secret"] = clientSecret,
}),
};
pedido.Headers.Add("x-api-key", apiKey);

using var resposta = await http.SendAsync(pedido, ct);
resposta.EnsureSuccessStatusCode();
var corpo = await resposta.Content.ReadFromJsonAsync<RespostaToken>(ct)
?? throw new InvalidOperationException("Resposta de token vazia.");

_token = corpo.AccessToken;
_venceEm = DateTimeOffset.UtcNow.AddSeconds(corpo.ExpiresIn);
return _token;
}
finally
{
_trava.Release();
}
}

/// Chame ao receber 401 numa rota de integração, antes de repetir a chamada uma vez.
public void Descartar() => _token = null;

sealed record RespostaToken(
[property: JsonPropertyName("access_token")] string AccessToken,
[property: JsonPropertyName("expires_in")] int ExpiresIn);
}

O HttpClient do .NET não manda User-Agent por padrão. Configure-o uma vez:

http.BaseAddress = new Uri("https://vm-integration.staging.etechsa.com.br");
http.DefaultRequestHeaders.UserAgent.ParseAdd("MeuErp/1.0");