50

I have a WCF Webservice which checks if the user is valid.

If the user is valid I want to generate a token which expires after 24 hours.

public bool authenticateUserManual(string userName, string password,string language,string token)
{
    if (Membership.ValidateUser(userName,password))
    {
        //////////
        string token = ???? 
        //////////

        return true;
    }
    else 
    {
        return false;
    }
}   
Tom
  • 26,212
  • 21
  • 100
  • 111
Eray Geveci
  • 1,099
  • 4
  • 17
  • 39

6 Answers6

159

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.

Piotr Kula
  • 9,597
  • 8
  • 59
  • 85
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • How do I extract the token from the data variable? – SuperFrog Feb 25 '14 at 18:02
  • @UdiI: Why would you want to do that when the `data` variable is created from the token? Anyway, you would use `Convert.ToBase64String(data)` to recreate the token from the `data` variable. – Guffa Feb 25 '14 at 18:16
  • I want to create a token which consists timestamp, another token (e.g. a facebook token), and a userId - then encrypt this token and send to the user. Later on when the user make a call to my server he'll need to pass this token, and I will need the data this token contains. – SuperFrog Feb 25 '14 at 18:20
  • Convert.ToBase64String(data) will also include the timestamp, won't it? – SuperFrog Feb 25 '14 at 18:32
  • 6
    @UdiI: Yes, the token includes the timestamp. Do you mean that you want to get the GUID key? Then you wold use `new GUID(data.Skip(8).ToArray())`. – Guffa Feb 25 '14 at 18:44
  • 2
    Using this time solution and this encryption http://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndael.aspx you can create a nice secure token. – Piotr Kula May 21 '14 at 18:30
  • @Guffa Any equivalent examples in java . can you help me ? – Santhosh Oct 13 '14 at 12:26
  • @SanKrish: I'm not familiar with the Java libraries and what the corresponding methods would be, but the principle would be the same. – Guffa Oct 13 '14 at 13:07
  • @Guffa, in your code `string token = Convert.ToBase64String(Guid.NewGuid().ToByteArray());` is there any reason why you base64 encoded the Guid? Is there an advantage over just Guid.ToString()? Thanks! – badallen Apr 25 '16 at 22:10
  • @badallen: Just that it is shorter. – Guffa Apr 25 '16 at 23:52
  • 2
    @Guffa is it secure to use guid in a token – Dragon Mar 29 '17 at 20:25
  • where should i keep tokens to check whether or not they are valid in database or session variable ? thanks. – engtuncay Jan 03 '19 at 14:57
  • 1
    Guid.NewGuid() is predictable check https://stackoverflow.com/questions/816882/how-to-predict-the-next-guid-from-a-given-guid – Toolkit Oct 15 '19 at 13:50
  • While a Guid is "globally unique" it is technically NOT "cryptographically random", thus should probably not be used in a security context, such as a session identifier. – Mark Good Aug 30 '23 at 15:05
35

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:

  1. See the expiration of a token
  2. Use a guid to mask validate (global application guid or user guid)
  3. See if the token was provided for the purpose I created it (no reuse..)
  4. 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

GFoley83
  • 3,439
  • 2
  • 33
  • 46
  • this was a totally cool answer ;) I did a little bit of tweaking in order to accomodate my logics. – Vikneshwar May 14 '16 at 10:52
  • 1
    what about GetString() , cann you post the body ? – Jmocke Oct 12 '16 at 10:09
  • 1
    @Jmocke to use GetString() and GetBytes() set namespace to System.Text and then use Encoding.UTF8.GetString() / Encoding.UTF8.GetBytes() – McDee Mar 07 '17 at 11:16
  • 7
    I have this feeling that this example completely omits any security layer. There seems to be no cryptographic signature that would allow the token to be transmitted via insecure channels and ensure to not be tampered with. As it looks to me right now, anyone seeing this token could create valid variants of it that will pass through validation. **Beware, this answer seems to be insecure!** – Sven Jan 31 '18 at 17:04
  • Can you explain a little bit more on how you skip and take data? – Aruna Aug 13 '18 at 18:26
  • Fixed broken code above and working example here: https://dotnetfiddle.net/VWagqD – GFoley83 Jun 29 '20 at 05:32
3

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.

cuongle
  • 74,024
  • 28
  • 151
  • 206
0

you need to store the token while creating for 1st registration. When you retrieve data from login table you need to differentiate entered date with current date if it is more than 1 day (24 hours) you need to display message like your token is expired.

To generate key refer here

Linga
  • 10,379
  • 10
  • 52
  • 104
0

I also had a similar but slightly different problem. I needed a token that could be spent within 2 minutes on a different server. ServerA and serverB shares a private key. Of course in the user browser, the token is public.

enter image description here

The web server A creates a string with a datetime in it. Then it hashes the string. The user in the browser uses the token. When server B receives the token does the same hash and compares the result. ServerA and serverB run c# code.

The problem is similar but not the same, but maybe the code can help someone anyway ....

public class TokenHelper
{

    private static byte[] GetHash(string inputString)
    {
        using (HashAlgorithm algorithm = MD5.Create())
            return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
    }

    private static string GetHashString(string inputString)
    {
        StringBuilder sb = new StringBuilder();
        foreach (byte b in GetHash(inputString))
            sb.Append(b.ToString("X2"));

        return sb.ToString();
    }

    public static Guid GetTokenForGuest(EnumWebsiteName website, string privateKey)
    {            
        string datetime = DateTime.Now.ToString("dd/MM/yyyy hh:mm");
        var hashString = GetHashString($"{datetime}|{website}|{privateKey}");            
        return new Guid(hashString);
    }

    public static bool  CheckTokenForGuest(Guid token, EnumWebsiteName website, string privateKey)
    {
        string datetime = DateTime.Now.ToString("dd/MM/yyyy hh:mm");
        var hashString = GetHashString($"{datetime}|{website}|{privateKey}");
        var test1 = new Guid(hashString);
        if (test1.CompareTo(token)==0) {
            return true;
        }

        string datetime2 = DateTime.Now.AddMinutes(-1).ToString("dd/MM/yyyy hh:mm");
        var hashString2 = GetHashString($"{datetime2}|{website}|{privateKey}");
        var test2 = new Guid(hashString2);
        if (test2.CompareTo(token) == 0)
        {
            return true;
        }

        return false;
    }

}
Francesco
  • 47
  • 1
  • 4
  • 2 red flag for me : 1. MD5 is insecure 2. the way to compare token within 2 min is too hard boiled – J.Luan Jun 28 '22 at 17:26
  • Thanks for your comment. 1) Yes, MD5 is insecure but I use it because is fast and for my project is enough. It's easy to pick another hash algorithm anyway. 2) For the "hard boiled" part this class has only 46 lines. Server A calls one method, Server B calls the other. Since the token time-span is only 2 minutes the idea is to use an hash function rather than decrypt the string. I'm not sure that hash a string is faster than decrypt it. – Francesco Jul 06 '22 at 14:09
-2

This way a token will exist up-to 24 hours. here is the code to generate token which will valid up-to 24 Hours. this code we use but i did not compose it.

public static string GenerateToken()
{
    int month = DateTime.Now.Month;
    int day = DateTime.Now.Day;
    string token = ((day * 100 + month) * 700 + day * 13).ToString();
    return token;
}
Indi_Rain
  • 179
  • 5
  • 17