Thursday, June 19, 2014

Utility for two-factor verification with Google Authenticator

There are a lot of code samples out there for this particular task.  However I had trouble finding a good one that showed how to authenticate against several time intervals, for better user experience, so here is a code sample.

Some references:

Comments:

  • This class uses the Singleton Pattern, which I prefer over static methods because the singleton can be mocked more easily.
  • The GenerateSecretKey method creates an encrypted key to store with an individual user (e.g., using a custom SimpleMembership configuration)
  • The IsTwoFactorVerificationCodeValid method called by an external class such as a custom validator
  • The GetCurrentCountersWithBeforeAndAfterIntervals method is what calculates the previous, current, and future intervals

Code sample:

1 using System;
2 using System.Linq;
3 using System.Collections.Generic;
4 using System.Security.Cryptography;
5 using System.Text;
6
7 namespace MyNamespace
8 {
9 public class GoogleAuthenticatorUtility
10 {
11 private const int NUMBER_OF_DIGITS = 6;
12 private const int NUMBER_OF_SECONDS_IN_INTERVAL = 30;
13
14 private readonly DateTime _unixEpoch;
15
16 private GoogleAuthenticatorUtility()
17 {
18 _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
19 }
20
21 private static GoogleAuthenticatorUtility _instance;
22 public static GoogleAuthenticatorUtility Instance
23 {
24 get { _instance = _instance ?? new GoogleAuthenticatorUtility(); return _instance; }
25 set { _instance = value; }
26 }
27
28
29 public virtual bool IsTwoFactorVerificationCodeValid(string userSecret, string verificationCode)
30 {
31 int[] passwords = GenerateTimeBasedPasswords(userSecret);
32 string[] allPasswords = GeneratePasswords(passwords);
33 return allPasswords.Contains(verificationCode);
34 }
35
36 public virtual string GenerateSecretKey()
37 {
38 byte[] buffer = new byte[9];
39
40 using (RandomNumberGenerator rng = RNGCryptoServiceProvider.Create())
41 {
42 rng.GetBytes(buffer);
43 }
44
45 return Convert.ToBase64String(buffer)
46 .Substring(0, 10)
47 .Replace('/', '0')
48 .Replace('+', '1');
49 }
50
51
52 private int[] GenerateTimeBasedPasswords(string secret)
53 {
54 if (string.IsNullOrEmpty(secret))
55 {
56 throw new ArgumentException("Secret must not be null or empty", "secret");
57 }
58
59 long[] counters = GetCurrentCountersWithBeforeAndAfterIntervals();
60 int[] passwords = new int[counters.Length];
61
62 for (int i = 0; i < counters.Length; i++)
63 {
64 byte[] counter = BitConverter.GetBytes(counters[i]);
65
66 if (BitConverter.IsLittleEndian)
67 {
68 Array.Reverse(counter);
69 }
70
71 byte[] key = Encoding.ASCII.GetBytes(secret);
72
73 HMACSHA1 hmac = new HMACSHA1(key, true);
74
75 byte[] hash = hmac.ComputeHash(counter);
76
77 int offset = hash[hash.Length - 1] & 0xf;
78
79 int binary =
80 ((hash[offset] & 0x7f) << 24)
81 | ((hash[offset + 1] & 0xff) << 16)
82 | ((hash[offset + 2] & 0xff) << 8)
83 | (hash[offset + 3] & 0xff);
84
85 passwords[i] = binary % (int)Math.Pow(10, NUMBER_OF_DIGITS); // 6 digits
86 }
87
88 return passwords;
89 }
90
91 private long[] GetCurrentCountersWithBeforeAndAfterIntervals()
92 {
93 var counters = new long[3];
94 double totalSeconds = (DateTime.UtcNow - _unixEpoch).TotalSeconds;
95
96 counters[0] = (long)((totalSeconds - NUMBER_OF_SECONDS_IN_INTERVAL) / NUMBER_OF_SECONDS_IN_INTERVAL);
97 counters[1] = (long)(totalSeconds / NUMBER_OF_SECONDS_IN_INTERVAL);
98 counters[2] = (long)((totalSeconds + NUMBER_OF_SECONDS_IN_INTERVAL) / NUMBER_OF_SECONDS_IN_INTERVAL);
99
100 return counters;
101 }
102
103 private string[] GeneratePasswords(int[] timeBasedPasswords)
104 {
105 var passwords = new string[timeBasedPasswords.Length];
106 for (int i = 0; i < timeBasedPasswords.Length; i++)
107 {
108 passwords[i] = timeBasedPasswords[i].ToString(new string('0', NUMBER_OF_DIGITS));
109 }
110
111 return passwords;
112 }
113 }
114 }
115

Friday, June 13, 2014

Resolve DNN error “Violation of UNIQUE KEY constraint 'IX_PortalAlias'”

Encountered this error message after installing a DNN site in my local IIS and then browsing to my site:  “Violation of UNIQUE KEY constraint 'IX_PortalAlias'. Cannot insert duplicate key in object 'dbo.PortalAlias'. The duplicate key value is (default.aspx)”.

I resolved the problem by deleting all existing rows from the table dbo.PortalAlias.

Tuesday, June 10, 2014

Resolve ASP.NET MVC 4 Ajax.BeginForm full postback

I recently spent several hours trying to resolve a problem where my view using Ajax.BeginForm kept posting back a full view instead of inserting the results of an AJAX partial postback into my existing view.  I searched for hours and could not find what was wrong:

web.config appSettings were correct:

<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />


Microsoft jQuery Unobtrusive Ajax NuGet package installed and bundled:


1 bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
2 "~/Scripts/jquery.unobtrusive*",
3 "~/Scripts/jquery.validate*"));
4

Simple test controller:


1 public class TestAjaxController : Controller
2 {
3 public ActionResult TestAjaxIndex()
4 {
5 return View();
6 }
7
8 [HttpPost]
9 public PartialViewResult GetTestAjax()
10 {
11 return PartialView("_GetTestAjax");
12 }
13 }


View (TestAjaxIndex.cshtml) created:


1 @{
2 ViewBag.Title = "TestAjaxIndex";
3 }
4
5 <h2>TEST AJAX</h2>
6
7 <div id="testAjax"></div>
8
9 @using (Ajax.BeginForm("GetTestAjax", new AjaxOptions() {
10 UpdateTargetId = "testAjax", InsertionMode = InsertionMode.Replace }))
11 {
12 <input type="submit" value="Test" />
13 }
14
15 @Scripts.Render("~/bundles/jqueryval")

Partial view (_GetTestAjax.cshtml) created:


1 <span>GOT IT</span>

Whenever I submitted \TestAjax\TestAjaxIndex it would do a full postback to \TestAjax\GetTestAjax and show my “GOT IT” span, rather than inserting my “GOT IT” span into my “testAjax” div.


It turns out the problem is that I have a _Layout.cshtml view which renders my TestAjaxIndex view inside the body using @RenderBody(), and I neglected to render my “~/bundles/jqueryval” script bundle inside of a Scripts section.  Therefore my jquery.unobtrusive-ajax.js resource was loaded AFTER my jquery-1.10.2.js resource.


Here is the corrected version of my TestAjaxIndex.cshtml which solved my problem:


1 @{
2 ViewBag.Title = "TestAjaxIndex";
3 }
4
5 <h2>TEST AJAX</h2>
6
7 <div id="testAjax"></div>
8
9 @using (Ajax.BeginForm("GetTestAjax", new AjaxOptions() {
10 UpdateTargetId = "testAjax", InsertionMode = InsertionMode.Replace }))
11 {
12 <input type="submit" value="Test" />
13 }
14
15 @*use a Scripts section to fix the problem*@
16 @section Scripts {
17 @Scripts.Render("~/bundles/jqueryval")
18 }