namespace Domain.Helpers { public class TokenValidationResult { /// <summary>Signature is door ons gemaakt (dus niet gemanipuleerd).</summary> public bool SignatureValid { get; set; } /// <summary>Het token is nog geldig (niet verlopen, subject klopt, etc.).</summary> public bool IsValid { get; set; } public string Subject { get; set; } public long UnixExpiry { get; set; } } public static class ShortTokenHelper { public static string GenerateToken(string secret, string subject, TimeSpan validFor) { if (string.IsNullOrEmpty(secret)) { throw new ArgumentException("secret is empty"); } ArgumentNullException.ThrowIfNull(subject); long expiry = DateTimeOffset.UtcNow.Add(validFor).ToUnixTimeSeconds(); string data = $"{expiry}:{subject}"; byte[] secretKey = Encoding.UTF8.GetBytes(secret); using HMACSHA256 hmac = new(secretKey); string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data))).Replace("+", "-").Replace("/", "_").TrimEnd('='); // Base64 encode only the subject to handle dots properly string encodedSubject = Convert.ToBase64String(Encoding.UTF8.GetBytes(subject)); return $"{expiry}.{encodedSubject}.{signature}"; } public static TokenValidationResult ValidateToken(string secret, string expectedSubject, string tokenInput) { TokenValidationResult result = new() { SignatureValid = false, IsValid = false }; ArgumentNullException.ThrowIfNull(tokenInput); if (string.IsNullOrEmpty(tokenInput)) { throw new ArgumentException("tokenInput is empty"); } try { // Token format is now: expiry.encodedSubject.signature (no base64 encoding of entire token) string[] parts = tokenInput.Split('.'); if (parts.Length != 3) { return result; } if (!long.TryParse(parts[0], out long expiry)) { return result; } // Decode the base64 encoded subject byte[] subjectBytes = Convert.FromBase64String(parts[1]); string subjectFromToken = Encoding.UTF8.GetString(subjectBytes); string signature = parts[2]; // Vul altijd expiry en subject, ook als signature niet klopt result.Subject = subjectFromToken; result.UnixExpiry = expiry; string data = $"{expiry}:{subjectFromToken}"; byte[] secretKey = Encoding.UTF8.GetBytes(secret); using HMACSHA256 hmac = new(secretKey); string expectedSig = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data))).Replace("+", "-").Replace("/", "_").TrimEnd('='); // Zet altijd SignatureValid als de handtekening klopt result.SignatureValid = signature == expectedSig; // Alleen geldig als ook signature klopt én checks slagen if (result.SignatureValid && subjectFromToken == expectedSubject && DateTimeOffset.UtcNow.ToUnixTimeSeconds() <= expiry) { result.IsValid = true; } return result; } catch (FormatException) { // Invalid base64 format return result; } } } }
🧩 ShortTokenHelper – uitleg & gebruik
Deze helper maakt en valideert een kort HMAC-beveiligd token voor tijdelijke authenticatie of verificatie (zoals login-links).
Het tokenformaat is:
<unixExpiry>.<base64(subject)>.<signature>
🔐 Token genereren
string secretKey = "sterk-geheim"; string token = ShortTokenHelper.GenerateToken(secretKey, "user@email.com", TimeSpan.FromDays(3));
→ Output is een compact token dat 3 dagen geldig is.
✅ Token valideren
var result = ShortTokenHelper.ValidateToken(secretKey, "user@email.com", token); if (result.IsValid) { // Token is echt, subject klopt, en nog niet verlopen } else if (result.SignatureValid) { // Signature klopt, maar subject of expiry niet }
📘 Structuur van TokenValidationResult
SignatureValid
– token niet gemanipuleerdIsValid
– token geldig én nog niet verlopenSubject
– email of identifier uit tokenUnixExpiry
– vervaltijdstip in Unix seconden
🧠 Gebruiksscenario
Wordt o.a. gebruikt voor login-links, “magic links”, of tijdelijke API-verificatie zonder zware JWT.
979000cookie-checkC# ShortTokenHelper