Human Captcha (Not Robot) React Component with .Net Core WebApi Backend
A captcha is a challenge response test to determine whether or not the user is human. We may create a React human captcha component with a .Net Core Web API back-end.
Back-end
Our back-end is ASP.Net Core Web API 3.1. Back-end will generate a truly random alpha-numeric captcha value, creates a binary image using System.Drawing namespace. Back-end will verify the user input value against the generated value and return an authentication Token to be used in Forms.
The method that generates a random captcha value and creates a binary image is as follows:
/// generate captcha image
/// </summary>
/// <param name="size">alphanumeric length of the captcha</param>
/// <returns>image properties and image binary data</returns>
private async Task<CaptchaImage> generateCaptcha(int size)
{
try
{
int width = size * 30;
int height = width / 4;
var b = new Bitmap(width, height);
var g = Graphics.FromImage(b);
//create brush and rectangle
var brush = new HatchBrush(HatchStyle.SmallConfetti, Color.LightSteelBlue, Color.White);
g.FillRectangle(brush, 0, 0, b.Width, b.Height);
float emSize = width / size;
var fnt = new Font("Arial", emSize, FontStyle.Italic);
// generate truly random captcha value
string value = generateRandomCaptchaValue(size);
while (string.IsNullOrEmpty(value))
value = generateRandomCaptchaValue(size);
// write the generated value on the image
g.DrawString(value, fnt, Brushes.Coral, 0, 0, StringFormat.GenericTypographic);
// generate random noise on the image
brush = new HatchBrush(HatchStyle.LargeConfetti, Color.SlateBlue, Color.SlateGray);
fillRandomNoise(g, brush, width, height);
// draw random lines on the image
Point[] iP = getRandomPoints(width, height);
for (int i = 0; i < 3; i++)
{
Brush brs = Brushes.BlueViolet;
if (i % 3 == 1)
brs = Brushes.DodgerBlue;
if (i % 3 == 2)
brs = Brushes.MediumVioletRed;
Pen pn = new Pen(brs, 2);
g.DrawBezier(pn, iP[i * 4], iP[i * 4 + 1], iP[i * 4 + 2], iP[i * 4 + 3]);
}
// create image
byte[] bBuffer = (byte[])System.ComponentModel.TypeDescriptor.GetConverter(b).ConvertTo(b, typeof(byte[]));
// add image properties to database
var guid = await saveCaptcha(value);
// return image with properties
if (guid.HasValue)
return new CaptchaImage() { ID = guid.Value, Value = value, Data = bBuffer, MimeType = "image/jpg" };
}
catch(System.Exception e)
{
exceptionManager.DoException(e);
}
return null;
}
The method verifyCaptcha verifies the user input value against the generated value and returns an authentication Token to the client to be used in Forms
private async Task<ResultItem> verifyCaptcha(Guid imageid, string value)
{
try
{
var item = await context.Captchas.Where(c => c.Guid.Equals(imageid) && c.Value.Equals(value) && c.Expires >= DateTime.Now).FirstOrDefaultAsync();
if (item != null)
{
var tokenModel = new TokenModel(this.context, this.exceptionManager);
string token = await tokenModel.SaveToken(imageid);
if (!string.IsNullOrEmpty(token))
return new ResultItem() { Result = true, Token = token };
}
}
catch(System.Exception e)
{
exceptionManager.DoException(e);
}
return new ResultItem() { Result = false, Token = string.Empty };
}
Front-end
Our React component will request from the back-end to generate a random captcha value and the captcha image that the generated value is drawn on it. React component includes the captcha image and a text field and a button to verify the user is not a robot.
var id = "";
var url = "";
try {
const response = await fetch(API + QUERY + "/" + size);
const data = await response.json();
id = data.id;
const blob = this.convertToImage(data.data, data.mimeType);
url = URL.createObjectURL(blob);
}
catch (error) {
console.log(error);
}
this.setState({ id: id, url: url, size: size });
}
Verify button will request from back-end if the user input value is matched with the generated value. If the captcha is verified, human captcha React component will pass an authentication Token provided from back-end to its parent component.
let url = API + QUERY;
let data = { id: id, value: value };
var result = false;
var token = "";
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
const resultData = await response.json();
if (resultData) {
result = resultData.result;
token = resultData.token;
}
}
catch (error) {
console.log(error);
}
this.setState({ token: token, result: result });
callback();
}
Parent component that uses human captcha component will use the authentication token provided from its child human captcha component to submit the Form.
Token must be provided in the header of the post request when submitting the form:
The post method that handles the form submit in the back-end controller will use the authentication token before processing. This could be achieved by generating a class TokenAuthenticationAttribute that inherits ActionFilterAttribute and decorating the method with TokenAuthentication (*).
TokenAuthentication action filter should be defined in ConfigureServices of startup for dependency injection:
All source codes are included in a GitHub repository. Human Captcha github repository contains a .net core web-api back-end project and React front-end project.
(*) For details take a look at the blog post about Filter Based Authorization
Comments
Post a Comment