Created
January 31, 2020 15:47
-
-
Save jmichas/dab35d9b8e916eae8936322465d76b66 to your computer and use it in GitHub Desktop.
4 Digit Token Provider For Asp.net Core Identity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services.AddIdentity<ApplicationUser, IdentityRole>() | |
... | |
.AddTokenProvider<FourDigitTokenProvider>(FourDigitTokenProvider.FourDigitEmail) | |
.AddTokenProvider<FourDigitTokenProvider>(FourDigitTokenProvider.FourDigitPhone) | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
also shoulde be add this configuration
services.AddIdentity<ApplicationUser, IdentityRole>(options => { options.Tokens.ChangePhoneNumberTokenProvider = FourDigitTokenProvider.FourDigitPhone; options.Tokens.EmailConfirmationTokenProvider = FourDigitTokenProvider.FourDigitEmail; })