how to generate a unique token which expires after 24 hours?
There are two possible approaches; either you create a unique value and store somewhere along with the creation time, for example in a database, or you put the creation time inside the token so that you can decode it later and see when it was created.
To create a unique token:
string token = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
Basic example of creating a unique token containing a time stamp:
byte[] time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary());byte[] key = Guid.NewGuid().ToByteArray();string token = Convert.ToBase64String(time.Concat(key).ToArray());
To decode the token to get the creation time:
byte[] data = Convert.FromBase64String(token);DateTime when = DateTime.FromBinary(BitConverter.ToInt64(data, 0));if (when < DateTime.UtcNow.AddHours(-24)) { // too old}
Note: If you need the token with the time stamp to be secure, you need to encrypt it. Otherwise a user could figure out what it contains and create a false token.
I like Guffa's answer and since I can't comment I will provide the answer Udil's question here.
I needed something similar but I wanted certein logic in my token, I wanted to:
- See the expiration of a token
- Use a guid to mask validate (global application guid or user guid)
- See if the token was provided for the purpose I created it (no reuse..)
- See if the user I send the token to is the user that I am validating it for
Now points 1-3 are fixed length so it was easy, here is my code:
Here is my code to generate the token:
public string GenerateToken(string reason, MyUser user){ byte[] _time = BitConverter.GetBytes(DateTime.UtcNow.ToBinary()); byte[] _key = Guid.Parse(user.SecurityStamp).ToByteArray(); byte[] _Id = GetBytes(user.Id.ToString()); byte[] _reason = GetBytes(reason); byte[] data = new byte[_time.Length + _key.Length + _reason.Length+_Id.Length]; System.Buffer.BlockCopy(_time, 0, data, 0, _time.Length); System.Buffer.BlockCopy(_key , 0, data, _time.Length, _key.Length); System.Buffer.BlockCopy(_reason, 0, data, _time.Length + _key.Length, _reason.Length); System.Buffer.BlockCopy(_Id, 0, data, _time.Length + _key.Length + _reason.Length, _Id.Length); return Convert.ToBase64String(data.ToArray());}
Here is my Code to take the generated token string and validate it:
public TokenValidation ValidateToken(string reason, MyUser user, string token){ var result = new TokenValidation(); byte[] data = Convert.FromBase64String(token); byte[] _time = data.Take(8).ToArray(); byte[] _key = data.Skip(8).Take(16).ToArray(); byte[] _reason = data.Skip(24).Take(2).ToArray(); byte[] _Id = data.Skip(26).ToArray(); DateTime when = DateTime.FromBinary(BitConverter.ToInt64(_time, 0)); if (when < DateTime.UtcNow.AddHours(-24)) { result.Errors.Add( TokenValidationStatus.Expired); } Guid gKey = new Guid(_key); if (gKey.ToString() != user.SecurityStamp) { result.Errors.Add(TokenValidationStatus.WrongGuid); } if (reason != GetString(_reason)) { result.Errors.Add(TokenValidationStatus.WrongPurpose); } if (user.Id.ToString() != GetString(_Id)) { result.Errors.Add(TokenValidationStatus.WrongUser); } return result;}private static string GetString(byte[] reason) => Encoding.ASCII.GetString(reason);private static byte[] GetBytes(string reason) => Encoding.ASCII.GetBytes(reason);
The TokenValidation class looks like this:
public class TokenValidation{ public bool Validated { get { return Errors.Count == 0; } } public readonly List<TokenValidationStatus> Errors = new List<TokenValidationStatus>();}public enum TokenValidationStatus{ Expired, WrongUser, WrongPurpose, WrongGuid}
Now I have an easy way to validate a token, no Need to Keep it in a list for 24 hours or so.Here is my Good-Case Unit test:
private const string ResetPasswordTokenPurpose = "RP";private const string ConfirmEmailTokenPurpose = "EC";//change here change bit length for reason section (2 per char)[TestMethod]public void GenerateTokenTest(){ MyUser user = CreateTestUser("name"); user.Id = 123; user.SecurityStamp = Guid.NewGuid().ToString(); var token = sit.GenerateToken(ConfirmEmailTokenPurpose, user); var validation = sit.ValidateToken(ConfirmEmailTokenPurpose, user, token); Assert.IsTrue(validation.Validated,"Token validated for user 123");}
One can adapt the code for other business cases easely.
Happy Coding
Walter
Use Dictionary<string, DateTime>
to store token with timestamp:
static Dictionary<string, DateTime> dic = new Dictionary<string, DateTime>();
Add token with timestamp whenever you create new token:
dic.Add("yourToken", DateTime.Now);
There is a timer running to remove any expired tokens out of dic:
timer = new Timer(1000*60); //assume run in 1 minute timer.Elapsed += timer_Elapsed; static void timer_Elapsed(object sender, ElapsedEventArgs e) { var expiredTokens = dic.Where(p => p.Value.AddDays(1) <= DateTime.Now) .Select(p => p.Key); foreach (var key in expiredTokens) dic.Remove(key); }
So, when you authenticate token, just check whether token exists in dic or not.