Skip to main content

C# Example

Using System.IdentityModel.Tokens.Jwt for JWT signing.

Install

dotnet add package System.IdentityModel.Tokens.Jwt

Complete example

using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.IdentityModel.Tokens;

// --- Configuration ---
var partnerId = "your-partner-id-uuid";
var apiKeyId = "your-api-key-id-uuid";
var privateKeyPem = File.ReadAllText("private-key.pem");
var tokenEndpoint = "https://prod.rynopay.io/auth/partner/v1/token";
var apiBaseUrl = "https://prod.rynopay.io";

// --- Token Manager ---
var tokenManager = new TokenManager(partnerId, apiKeyId, privateKeyPem, tokenEndpoint);

// --- Making API calls ---
using var httpClient = new HttpClient { BaseAddress = new Uri(apiBaseUrl) };

var token = await tokenManager.GetTokenAsync();
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

var response = await httpClient.GetAsync("/api/partner/some-endpoint");

// Handle expired token
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
tokenManager.Clear();
token = await tokenManager.GetTokenAsync();
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
response = await httpClient.GetAsync("/api/partner/some-endpoint");
}

response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync();
Console.WriteLine(data);

// --- TokenManager class ---
public class TokenManager
{
private readonly string _partnerId;
private readonly string _apiKeyId;
private readonly ECDsaSecurityKey _signingKey;
private readonly string _tokenEndpoint;
private readonly HttpClient _httpClient = new();

private string? _cachedToken;
private DateTime _expiresAt = DateTime.MinValue;

public TokenManager(string partnerId, string apiKeyId, string privateKeyPem, string tokenEndpoint)
{
_partnerId = partnerId;
_apiKeyId = apiKeyId;
_tokenEndpoint = tokenEndpoint;

var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(privateKeyPem);
_signingKey = new ECDsaSecurityKey(ecdsa);
}

public async Task<string> GetTokenAsync()
{
// Return cached token if still valid (with 10-minute buffer)
if (_cachedToken != null && DateTime.UtcNow < _expiresAt.AddMinutes(-10))
{
return _cachedToken;
}

// Build the JWT assertion
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _partnerId,
Audience = "https://prod.rynopay.io/auth/partner/v1/token",
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, _partnerId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
IssuedAt = now,
Expires = now.AddMinutes(5),
SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.EcdsaSha256),
AdditionalHeaderClaims = new Dictionary<string, object> { { "kid", _apiKeyId } }
};

var handler = new JwtSecurityTokenHandler();
var assertion = handler.CreateEncodedJwt(tokenDescriptor);

// Exchange for an access token
var response = await _httpClient.PostAsJsonAsync(_tokenEndpoint, new
{
grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion,
scope = "partner.read partner.write"
});

if (!response.IsSuccessStatusCode)
{
var errorBody = await response.Content.ReadAsStringAsync();
throw new Exception($"Token exchange failed: {errorBody}");
}

var tokenResponse = await response.Content.ReadFromJsonAsync<JsonElement>();
_cachedToken = tokenResponse.GetProperty("accessToken").GetString()!;
var expiresIn = tokenResponse.GetProperty("expiresIn").GetInt32();
_expiresAt = DateTime.UtcNow.AddSeconds(expiresIn);

return _cachedToken;
}

public void Clear()
{
_cachedToken = null;
_expiresAt = DateTime.MinValue;
}
}