ASP.NET Core Image Verification Code Implementation with Backend Validation
// Verification method
public static string VerificationCodeCacheFormat = "vcode_cache_{0}";
public IActionResult GenerateVerificationCode()
{
var verificationService = new VerificationCodeService();
string code = "";
using (var memoryStream = verificationService.CreateVerificationImage(out code))
{
code = code.ToLower(); // Case-insensitive verification code
var token = Guid.NewGuid().ToString();
var cacheKey = string.Format(VerificationCodeCacheFormat, token);
_memoryCache.Set(cacheKey, code, new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(20)));
Response.Cookies.Append("verification_token", token);
return File(memoryStream.ToArray(), "image/png");
}
}
// Backend validation method
public bool ValidateUserInput(string userToken, string userInput)
{
var cacheKey = string.Format(VerificationCodeCacheFormat, userToken);
string storedCode = "";
if (!_memoryCache.TryGetValue(cacheKey, out storedCode))
return false;
if (storedCode.ToLower() != userInput.ToLower())
return false;
_memoryCache.Remove(cacheKey);
return true;
}
Generating verifiaction codes and images
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Security.Services
{
/// <summary>
/// Image verification code generator
/// </summary>
public class VerificationCodeService
{
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="length">Length of the string</param>
/// <returns>Random alphanumeric string</returns>
private string GenerateRandomString(int length)
{
// Character set: 0-9, a-z, A-Z
string chars = "0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,P,Q,R,S,T,U,V,W,X,Y,Z";
string[] charArray = chars.Split(',');
string code = "";
int previousValue = -1;
Random random = new Random();
for (int i = 1; i <= length; i++)
{
if (previousValue != -1)
{
random = new Random(i * previousValue * unchecked((int)DateTime.Now.Ticks));
}
int currentValue = random.Next(61);
if (previousValue == currentValue)
{
return GenerateRandomString(length); // Recursively generate if duplicate
}
previousValue = currentValue;
code += charArray[currentValue];
}
return code;
}
/// <summary>
/// Create verification image from string
/// </summary>
/// <param name="code">Verification code string</param>
/// <param name="length">Number of characters (default 4)</param>
/// <returns>Memory stream containing PNG image</returns>
public MemoryStream CreateVerificationImage(out string code, int length = 4)
{
code = GenerateRandomString(length);
Bitmap image = null;
Graphics graphics = null;
MemoryStream memoryStream = null;
Random random = new Random();
// Color palette
Color[] colors = { Color.Black, Color.Red, Color.DarkBlue, Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple };
// Font collection
string[] fonts = { "Verdana", "Microsoft Sans Serif", "Comic Sans MS", "Arial", "SimSun" };
// Create image
image = new Bitmap((int)code.Length * 18, 32);
graphics = Graphics.FromImage(image);
graphics.Clear(Color.White);
// Draw background noise
for (int i = 0; i < 100; i++)
{
int x = random.Next(image.Width);
int y = random.Next(image.Height);
graphics.DrawRectangle(new Pen(Color.LightGray, 0), x, y, 1, 1);
}
// Draw verification code
for (int i = 0; i < code.Length; i++)
{
int colorIndex = random.Next(7);
int fontIndex = random.Next(4);
Font font = new Font(fonts[fontIndex], 15, FontStyle.Bold);
Brush brush = new SolidBrush(colors[colorIndex]);
int yPosition = 4;
if ((i + 1) % 2 == 0)
{
yPosition = 2;
}
graphics.DrawString(code.Substring(i, 1), font, brush, 3 + (i * 12), yPosition);
}
memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Png);
graphics.Dispose();
image.Dispose();
return memoryStream;
}
}
}
The above implementation works for a single vreification code per page. Here's an improved version that supports multiple verfiication codes:
// Modified GenerateVerificationCode method to support multiple codes on a page
public IActionResult GenerateVerificationCode(string identifier = "")
{
var verificationService = new VerificationCodeService();
string code = "";
using (var memoryStream = verificationService.CreateVerificationImage(out code))
{
code = code.ToLower();
var token = Guid.NewGuid().ToString();
var cacheKey = string.Format(VerificationCodeCacheFormat, token);
_memoryCache.Set(cacheKey, code, new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(20)));
Response.Cookies.Append($"verification_token_{identifier}", token);
return File(memoryStream.ToArray(), "image/png");
}
}
Single verification code on a page:
<p>
Please enter the verification code: <br>
<input type="text" id="verificationCode" class="form-control" />
<img id="verificationImage" src="~/Home/GenerateVerificationCode" alt="Can't see clearly? Click to refresh"
onclick="this.src = this.src + '?'" style="vertical-align:middle;" />
</p>
Multiple verification codes on a page:
<p>
<input name="verification_code" type="text" maxlength="5" id="otherCode" class="form-control tbText">
<img id="otherVerificationImage" src="~/Home/GenerateVerificationCode?identifier=other" alt="Can't see clearly? Click to refresh"
onclick="this.src = 'GenerateVerificationCode?identifier=other&' + Math.random()" style="vertical-align:middle;" />
</p>