Skip to content

Instantly share code, notes, and snippets.

@ArtemAvramenko
Created May 26, 2017 12:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ArtemAvramenko/8f34ec8aac5c0fb6380871a18e69ffd8 to your computer and use it in GitHub Desktop.
Save ArtemAvramenko/8f34ec8aac5c0fb6380871a18e69ffd8 to your computer and use it in GitHub Desktop.
ASP.NET Identity: Add ability to have different token lifespan for different purposes
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security.DataProtection;
/// <summary>
/// Token provider that uses an IDataProtector to generate encrypted tokens based off of the security stamp
/// </summary>
public class DataProtectorTokenProviderEx<TUser, TKey> : IUserTokenProvider<TUser, TKey>
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
/// <summary>
/// Constructor
/// </summary>
public DataProtectorTokenProviderEx(IDataProtector protector)
{
if (protector == null)
{
throw new ArgumentNullException("protector");
}
Protector = protector;
TokenLifespan = TimeSpan.FromDays(1.0);
}
/// <summary>
/// IDataProtector for the token
/// </summary>
public IDataProtector Protector
{
get;
private set;
}
/// <summary>
/// Lifespan after which the token is considered expired
/// </summary>
public TimeSpan TokenLifespan
{
get;
set;
}
/// <summary>
/// Generate a protected string for a user
/// </summary>
public async Task<string> GenerateAsync(string purpose, UserManager<TUser, TKey> manager, TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
var memoryStream = new MemoryStream();
using (var binaryWriter = new BinaryWriter(memoryStream, new UTF8Encoding(false, true), true))
{
binaryWriter.Write(DateTimeOffset.UtcNow.UtcTicks);
binaryWriter.Write(Convert.ToString(user.Id, CultureInfo.InvariantCulture));
binaryWriter.Write(purpose ?? "");
string stamp = null;
if (manager.SupportsUserSecurityStamp)
{
stamp = await manager.GetSecurityStampAsync(user.Id);
}
binaryWriter.Write(stamp ?? "");
}
byte[] token = this.Protector.Protect(memoryStream.ToArray());
return Convert.ToBase64String(token);
}
/// <summary>
/// Return false if the token is not valid
/// </summary>
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser, TKey> manager, TUser user)
{
try
{
var buffer = Protector.Unprotect(Convert.FromBase64String(token));
var stream = new MemoryStream(buffer);
using (var binaryReader = new BinaryReader(stream, new UTF8Encoding(false, true), true))
{
var dateTimeOffset = new DateTimeOffset(binaryReader.ReadInt64(), TimeSpan.Zero);
string tokenId = binaryReader.ReadString();
if (!string.Equals(tokenId, Convert.ToString(user.Id, CultureInfo.InvariantCulture)))
{
return false;
}
string tokenPurpose = binaryReader.ReadString();
if (!string.Equals(tokenPurpose, purpose))
{
return false;
}
/////////////////////////////////////////////////
// INSERT CUSTOM TOKEN LIFE SPAN VALIDATION HERE
if (purpose != "Confirmation")
{
var left = dateTimeOffset + TokenLifespan;
if (left < DateTimeOffset.UtcNow)
{
return false;
}
}
/////////////////////////////////////////////////
string tokenStamp = binaryReader.ReadString();
if (binaryReader.PeekChar() != -1)
{
return false;
}
var stamp = "";
if (manager.SupportsUserSecurityStamp)
{
stamp = await manager.GetSecurityStampAsync(user.Id);
}
return tokenStamp == stamp;
}
}
catch
{
return false;
}
}
/// <summary>
/// Returns true if the provider can be used to generate tokens for this user
/// </summary>
public Task<bool> IsValidProviderForUserAsync(UserManager<TUser, TKey> manager, TUser user)
{
return Task.FromResult(true);
}
/// <summary>
/// This provider no-ops by default when asked to notify a user
/// </summary>
public Task NotifyAsync(string token, UserManager<TUser, TKey> manager, TUser user)
{
return Task.FromResult(0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment