{"id":9790,"date":"2025-10-08T07:53:50","date_gmt":"2025-10-08T06:53:50","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=9790"},"modified":"2025-10-08T07:53:51","modified_gmt":"2025-10-08T06:53:51","slug":"c-shorttokenhelper","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/c-shorttokenhelper\/","title":{"rendered":"C# ShortTokenHelper"},"content":{"rendered":"\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">namespace Domain.Helpers\n{\n    public class TokenValidationResult\n    {\n        \/\/\/ &lt;summary>Signature is door ons gemaakt (dus niet gemanipuleerd).&lt;\/summary>\n        public bool SignatureValid { get; set; }\n\n        \/\/\/ &lt;summary>Het token is nog geldig (niet verlopen, subject klopt, etc.).&lt;\/summary>\n        public bool IsValid { get; set; }\n        public string Subject { get; set; }\n        public long UnixExpiry { get; set; }\n    }\n\n    public static class ShortTokenHelper\n    {\n        public static string GenerateToken(string secret, string subject, TimeSpan validFor)\n        {\n            if (string.IsNullOrEmpty(secret))\n            {\n                throw new ArgumentException(\"secret is empty\");\n            }\n\n            ArgumentNullException.ThrowIfNull(subject);\n\n            long expiry = DateTimeOffset.UtcNow.Add(validFor).ToUnixTimeSeconds();\n            string data = $\"{expiry}:{subject}\";\n\n            byte[] secretKey = Encoding.UTF8.GetBytes(secret);\n\n            using HMACSHA256 hmac = new(secretKey);\n            string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data))).Replace(\"+\", \"-\").Replace(\"\/\", \"_\").TrimEnd('=');\n\n            \/\/ Base64 encode only the subject to handle dots properly\n            string encodedSubject = Convert.ToBase64String(Encoding.UTF8.GetBytes(subject));\n\n            return $\"{expiry}.{encodedSubject}.{signature}\";\n        }\n\n        public static TokenValidationResult ValidateToken(string secret, string expectedSubject, string tokenInput)\n        {\n            TokenValidationResult result = new() { SignatureValid = false, IsValid = false };\n\n            ArgumentNullException.ThrowIfNull(tokenInput);\n\n            if (string.IsNullOrEmpty(tokenInput))\n            {\n                throw new ArgumentException(\"tokenInput is empty\");\n            }\n\n            try\n            {\n                \/\/ Token format is now: expiry.encodedSubject.signature (no base64 encoding of entire token)\n                string[] parts = tokenInput.Split('.');\n                if (parts.Length != 3)\n                {\n                    return result;\n                }\n\n                if (!long.TryParse(parts[0], out long expiry))\n                {\n                    return result;\n                }\n\n                \/\/ Decode the base64 encoded subject\n                byte[] subjectBytes = Convert.FromBase64String(parts[1]);\n                string subjectFromToken = Encoding.UTF8.GetString(subjectBytes);\n                string signature = parts[2];\n\n                \/\/ Vul altijd expiry en subject, ook als signature niet klopt\n                result.Subject = subjectFromToken;\n                result.UnixExpiry = expiry;\n\n                string data = $\"{expiry}:{subjectFromToken}\";\n                byte[] secretKey = Encoding.UTF8.GetBytes(secret);\n\n                using HMACSHA256 hmac = new(secretKey);\n                string expectedSig = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data))).Replace(\"+\", \"-\").Replace(\"\/\", \"_\").TrimEnd('=');\n\n                \/\/ Zet altijd SignatureValid als de handtekening klopt\n                result.SignatureValid = signature == expectedSig;\n\n                \/\/ Alleen geldig als ook signature klopt \u00e9n checks slagen\n                if (result.SignatureValid &amp;&amp; subjectFromToken == expectedSubject &amp;&amp; DateTimeOffset.UtcNow.ToUnixTimeSeconds() &lt;= expiry)\n                {\n                    result.IsValid = true;\n                }\n\n                return result;\n            }\n            catch (FormatException)\n            {\n                \/\/ Invalid base64 format\n                return result;\n            }\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83e\udde9 <strong>ShortTokenHelper \u2013 uitleg &amp; gebruik<\/strong><\/h3>\n\n\n\n<p>Deze helper maakt en valideert een <strong>kort HMAC-beveiligd token<\/strong> voor tijdelijke authenticatie of verificatie (zoals login-links).<br>Het tokenformaat is:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;unixExpiry>.&lt;base64(subject)>.&lt;signature><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83d\udd10 <strong>Token genereren<\/strong><\/h4>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">string secretKey = \"sterk-geheim\";\nstring token = ShortTokenHelper.GenerateToken(secretKey, \"user@email.com\", TimeSpan.FromDays(3));<\/pre>\n\n\n\n<p>\u2192 Output is een compact token dat 3 dagen geldig is.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\u2705 <strong>Token valideren<\/strong><\/h4>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">var result = ShortTokenHelper.ValidateToken(secretKey, \"user@email.com\", token);\n\nif (result.IsValid)\n{\n    \/\/ Token is echt, subject klopt, en nog niet verlopen\n}\nelse if (result.SignatureValid)\n{\n    \/\/ Signature klopt, maar subject of expiry niet\n}<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83d\udcd8 <strong>Structuur van <code>TokenValidationResult<\/code><\/strong><\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>SignatureValid<\/code> \u2013 token niet gemanipuleerd<\/li>\n\n\n\n<li><code>IsValid<\/code> \u2013 token geldig \u00e9n nog niet verlopen<\/li>\n\n\n\n<li><code>Subject<\/code> \u2013 email of identifier uit token<\/li>\n\n\n\n<li><code>UnixExpiry<\/code> \u2013 vervaltijdstip in Unix seconden<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83e\udde0 <strong>Gebruiksscenario<\/strong><\/h4>\n\n\n\n<p>Wordt o.a. gebruikt voor login-links, \u201cmagic links\u201d, of tijdelijke API-verificatie zonder zware JWT.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83e\udde9 ShortTokenHelper \u2013 uitleg &amp; gebruik Deze helper maakt en valideert een kort HMAC-beveiligd token voor tijdelijke authenticatie of verificatie (zoals login-links).Het tokenformaat is: \ud83d\udd10 Token genereren \u2192 Output is een compact token dat 3 dagen geldig is. \u2705 Token valideren \ud83d\udcd8 Structuur van TokenValidationResult \ud83e\udde0 Gebruiksscenario Wordt o.a. gebruikt voor login-links, \u201cmagic links\u201d, of [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-9790","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9790","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=9790"}],"version-history":[{"count":1,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9790\/revisions"}],"predecessor-version":[{"id":9791,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9790\/revisions\/9791"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=9790"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=9790"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=9790"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}