Skip to content

Instantly share code, notes, and snippets.

@jmichas
Created January 31, 2020 15:47
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save jmichas/dab35d9b8e916eae8936322465d76b66 to your computer and use it in GitHub Desktop.
Save jmichas/dab35d9b8e916eae8936322465d76b66 to your computer and use it in GitHub Desktop.
4 Digit Token Provider For Asp.net Core Identity
using System;
using System.Globalization;
using System.Threading.Tasks;
using Goplay.Id.Models;
using Microsoft.AspNetCore.Identity;
namespace YOURNAMESPAZE
{
public class FourDigitTokenProvider: PhoneNumberTokenProvider<ApplicationUser>
{
public static string FourDigitPhone = "4DigitPhone";
public static string FourDigitEmail = "4DigitEmail";
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<ApplicationUser> manager, ApplicationUser user)
{
return Task.FromResult(false);
}
public override async Task<string> GenerateAsync(string purpose, UserManager<ApplicationUser> manager, ApplicationUser user)
{
var token = new SecurityToken(await manager.CreateSecurityTokenAsync(user));
var modifier = await GetUserModifierAsync(purpose, manager, user);
var code = Rfc6238AuthenticationService.GenerateCode(token, modifier, 4).ToString("D4", CultureInfo.InvariantCulture);
return code;
}
public override async Task<bool> ValidateAsync(string purpose, string token, UserManager<ApplicationUser> manager, ApplicationUser user)
{
int code;
if (!Int32.TryParse(token, out code))
{
return false;
}
var securityToken = new SecurityToken(await manager.CreateSecurityTokenAsync(user));
var modifier = await GetUserModifierAsync(purpose, manager, user);
var valid = Rfc6238AuthenticationService.ValidateCode(securityToken, code, modifier, token.Length);
return valid;
}
public override Task<string> GetUserModifierAsync(string purpose, UserManager<ApplicationUser> manager, ApplicationUser user)
{
return base.GetUserModifierAsync(purpose, manager, user);
}
}
}
// Copyright (c) Microsoft Corporation, Inc. All rights reserved.
// Licensed under the MIT License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace YOURNAMESPACE
{
internal sealed class SecurityToken
{
private readonly byte[] _data;
public SecurityToken(byte[] data)
{
_data = (byte[])data.Clone();
}
internal byte[] GetDataNoClone()
{
return _data;
}
}
internal static class Rfc6238AuthenticationService
{
private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3);
private static readonly Encoding _encoding = new UTF8Encoding(false, true);
private static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier, int numberOfDigits = 6)
{
// # of 0's = length of pin
//const int mod = 1000000;
var mod = (int)Math.Pow(10, numberOfDigits);
// See https://tools.ietf.org/html/rfc4226
// We can add an optional modifier
var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier));
// Generate DT string
var offset = hash[hash.Length - 1] & 0xf;
Debug.Assert(offset + 4 < hash.Length);
var binaryCode = (hash[offset] & 0x7f) << 24
| (hash[offset + 1] & 0xff) << 16
| (hash[offset + 2] & 0xff) << 8
| (hash[offset + 3] & 0xff);
var code = binaryCode % mod;
return code;
}
private static byte[] ApplyModifier(byte[] input, string modifier)
{
if (String.IsNullOrEmpty(modifier))
{
return input;
}
var modifierBytes = _encoding.GetBytes(modifier);
var combined = new byte[checked(input.Length + modifierBytes.Length)];
Buffer.BlockCopy(input, 0, combined, 0, input.Length);
Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
return combined;
}
// More info: https://tools.ietf.org/html/rfc6238#section-4
private static ulong GetCurrentTimeStepNumber()
{
var delta = DateTime.UtcNow - _unixEpoch;
return (ulong)(delta.Ticks / _timestep.Ticks);
}
public static int GenerateCode(SecurityToken securityToken, string modifier = null, int numberOfDigits = 6)
{
if (securityToken == null)
{
throw new ArgumentNullException("securityToken");
}
// Allow a variance of no greater than 90 seconds in either direction
var currentTimeStep = GetCurrentTimeStepNumber();
using (var hashAlgorithm = new HMACSHA1(securityToken.GetDataNoClone()))
{
var code = ComputeTotp(hashAlgorithm, currentTimeStep, modifier, numberOfDigits);
return code;
}
}
public static bool ValidateCode(SecurityToken securityToken, int code, string modifier = null, int numberOfDigits = 6)
{
if (securityToken == null)
{
throw new ArgumentNullException("securityToken");
}
// Allow a variance of no greater than 90 seconds in either direction
var currentTimeStep = GetCurrentTimeStepNumber();
using (var hashAlgorithm = new HMACSHA1(securityToken.GetDataNoClone()))
{
for (var i = -2; i <= 2; i++)
{
var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier, numberOfDigits);
if (computedTotp == code)
{
return true;
}
}
}
// No match
return false;
}
}
}
services.AddIdentity<ApplicationUser, IdentityRole>()
...
.AddTokenProvider<FourDigitTokenProvider>(FourDigitTokenProvider.FourDigitEmail)
.AddTokenProvider<FourDigitTokenProvider>(FourDigitTokenProvider.FourDigitPhone)
...
@jmichas
Copy link
Author

jmichas commented Jan 31, 2020

This method can easily be used to create token providers with any length token/code. Also, you could change the timesteps to be compatible with other services like Google, since the default is 3 min window 90 seconds either way.

@AhmedHalo
Copy link

Wow thanks

@hasanxdev
Copy link

also shoulde be add this configuration

services.AddIdentity<ApplicationUser, IdentityRole>(options => { options.Tokens.ChangePhoneNumberTokenProvider = FourDigitTokenProvider.FourDigitPhone; options.Tokens.EmailConfirmationTokenProvider = FourDigitTokenProvider.FourDigitEmail; })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment