Bonnes pratiques
Recommandations pour utiliser le SDK de maniere securisee et efficace.
Securite des identifiants
Ne jamais stocker en clair
// MAUVAIS - Ne faites jamais ca
public static Guid ApiPassword = new Guid("xxxx-xxxx-xxxx");
// BON - Utilisez des variables d'environnement
public static Guid ApiPassword => new Guid(Environment.GetEnvironmentVariable("SE_API_PASSWORD"));
// BON - Utilisez un gestionnaire de secrets (Azure Key Vault, AWS Secrets Manager)
public static Guid ApiPassword => secretsManager.GetSecret("SecureExchanges:ApiPassword");
Protegez les fichiers de configuration
<!-- Chiffrez les sections sensibles du web.config -->
<appSettings configProtectionProvider="DataProtectionConfigurationProvider">
<EncryptedData>...</EncryptedData>
</appSettings>
Utilisez DataProtectionHelper pour stocker les identifiants (Recommande)
Le SDK fournit DataProtectionHelper qui utilise l'API Windows DPAPI avec la cle machine (DataProtectionScope.LocalMachine). Les donnees sont chiffrees et ne peuvent etre dechiffrees que sur la meme machine.
using SecureExchangesSDK.Helpers;
using SecureExchangesSDK.Models.Transport;
// 1. Creer un modele pour stocker vos identifiants
public class ApiCredentials
{
public Guid Serial { get; set; }
public Guid ApiUser { get; set; }
public Guid ApiPassword { get; set; }
public string Endpoint { get; set; }
}
// 2. Sauvegarder les identifiants (chiffres avec la cle machine)
public void SaveCredentials(ApiCredentials credentials, string filePath)
{
// Serialise en JSON puis chiffre avec DPAPI (cle machine)
string json = SerializationHelper.SerializeToJson(credentials);
DataProtectionHelper<ApiCredentials>.ProtectAndSaveToFile(json, filePath);
}
// 3. Charger les identifiants
public ApiCredentials LoadCredentials(string filePath)
{
// Dechiffre et deserialise automatiquement
// Utilise un cache en memoire pour les appels subsequents
return DataProtectionHelper<ApiCredentials>.GetUnprotectedAsType(filePath);
}
// 4. Utilisation dans votre application
public class SecureMessageService
{
private readonly ApiCredentials _credentials;
private const string CredentialsPath = @"C:\ProgramData\MyApp\credentials.dat";
public SecureMessageService()
{
_credentials = DataProtectionHelper<ApiCredentials>.GetUnprotectedAsType(CredentialsPath);
}
public string SendMessage(string recipientEmail, string subject, string body)
{
var recipients = new List<RecipientInfo> { new RecipientInfo { Email = recipientEmail } };
var args = new MutliRecipientArgs(
_credentials.Endpoint,
_credentials.Serial,
_credentials.ApiUser,
_credentials.ApiPassword,
recipients, body, subject,
null, null, SendMethodEnum.onlyEmail,
false, true, true, "fr-CA", 5, 1440
);
var response = MessageHelper.MultiRecipientMessage(args);
return response.Status == 200 ? response.RecipientsAnswer[0].Answer.URL : null;
}
}
Note
Fonctionnement de DataProtectionHelper :
- Utilise
ProtectedData.Protect()avecDataProtectionScope.LocalMachine - Les donnees sont chiffrees avec la cle machine Windows
- Seuls les processus s'executant sur la meme machine peuvent dechiffrer
- Inclut un cache en memoire pour eviter les lectures repetees du fichier
- Supporte un parametre
entropyoptionnel pour une securite supplementaire
Tip
Pour une securite accrue, vous pouvez ajouter une entropie personnalisee :
byte[] entropy = Encoding.UTF8.GetBytes("MonApplicationSecrete");
DataProtectionHelper<ApiCredentials>.ProtectAndSaveToFile(json, filePath, entropy);
var credentials = DataProtectionHelper<ApiCredentials>.GetUnprotectedAsType(filePath, true, entropy);
Performance
Reutilisez les instances
// BON - Service singleton
public class MessageService
{
private readonly Guid _serial;
private readonly Guid _apiUser;
private readonly Guid _apiPassword;
public MessageService(IConfiguration config)
{
_serial = new Guid(config["SecureExchanges:Serial"]);
_apiUser = new Guid(config["SecureExchanges:ApiUser"]);
_apiPassword = new Guid(config["SecureExchanges:ApiPassword"]);
}
public string Send(...) { ... }
}
// Enregistrement en singleton
services.AddSingleton<MessageService>();
Limitez les fichiers en memoire
// Pour les gros fichiers, utilisez FilesPath plutot que FileArgs
if (fileSize > 50 * 1024 * 1024) // > 50 MB
{
args.FilesPath = new List<string> { filePath };
}
else
{
args.BinaryFiles = new List<FileArgs> { new FileArgs(File.ReadAllBytes(filePath), ...) };
}
Gestion des erreurs
Toujours verifier le statut
var response = MessageHelper.MultiRecipientMessage(args);
// TOUJOURS verifier avant d'acceder aux donnees
if (response.Status != 200)
{
_logger.LogError($"Erreur {response.Status}: {response.Data}");
throw new Exception(response.Data);
}
// Maintenant on peut acceder aux donnees
var url = response.RecipientsAnswer[0].Answer.URL;
Implementez des retries
public async Task<T> ExecuteWithRetry<T>(Func<T> action, int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return action();
}
catch (Exception ex) when (IsTransientError(ex))
{
if (i == maxRetries - 1) throw;
await Task.Delay(1000 * (i + 1));
}
}
throw new Exception("Max retries exceeded");
}
Journalisation
Loggez les operations importantes
public string SendSecureMessage(string recipient, string subject)
{
_logger.LogInformation("Envoi message a {Recipient}", recipient);
var response = MessageHelper.MultiRecipientMessage(args);
if (response.Status == 200)
{
_logger.LogInformation("Message envoye. URL: {Url}", response.RecipientsAnswer[0].Answer.URL);
}
else
{
_logger.LogError("Echec envoi. Status: {Status}, Details: {Details}",
response.Status, response.Data);
}
return response.Status == 200 ? response.RecipientsAnswer[0].Answer.URL : null;
}
Ne loggez jamais de donnees sensibles
// MAUVAIS
_logger.LogDebug($"Envoi avec password: {args.Password}");
// BON
_logger.LogDebug("Envoi avec password: [MASQUE]");
Validation des donnees
Validez les emails avant envoi
using SecureExchangesSDK.Helpers;
foreach (var recipient in recipients)
{
if (!EmailHelper.IsValidEmail(recipient.Email))
{
throw new ArgumentException($"Email invalide: {recipient.Email}");
}
}
Validez les numeros de telephone
if (!string.IsNullOrEmpty(recipient.Phone))
{
if (!PhoneHelper.IsValidPhoneNumber(recipient.Phone))
{
throw new ArgumentException($"Telephone invalide: {recipient.Phone}");
}
}