Build a Secure SMS Verification Service with .NET – Full Code Walkthrough

This article introduces an open‑source SMS verification module, outlines essential security features such as code expiry, length limits, request throttling, and one‑time use, and provides complete C# implementations for generating, sending, and validating both image and SMS captchas.

Programmer DD
Programmer DD
Programmer DD
Build a Secure SMS Verification Service with .NET – Full Code Walkthrough

SMS verification is a common requirement for many applications, and the open‑source Captcha project provides a compliant, safe, and reliable solution.

Verification codes have a limited validity period.

Codes should not be too short or too long.

The same phone number cannot request codes too frequently.

Codes become invalid after use.

The Captcha project satisfies all these criteria.

The core utility for generating numeric codes uses a random function to produce a string of the requested length, typically six digits for a balance of security and usability.

using System;
using System.Collections.Generic;
using System.Text;

namespace Captcha.Util
{
    /// <summary>
    /// SMS verification code utility class
    /// </summary>
    public static class MsgCaptchaHelper
    {
        /// <summary>
        /// Generate a random numeric code of specified length
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        public static string CreateRandomNumber(int length)
        {
            Random random = new Random();
            StringBuilder sbMsgCode = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                sbMsgCode.Append(random.Next(0, 9));
            }
            return sbMsgCode.ToString();
        }
    }
}

The service layer implements the main business logic, including image‑captcha generation, SMS‑captcha generation, frequency control, and validation.

using Captcha.Dto;
using Captcha.Service.Contract;
using Captcha.Util;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Text;

namespace Captcha.Service
{
    public class CaptchaService : ICaptchaService
    {
        #region Private Fields
        private readonly IMemoryCache _cache;
        private readonly IHostingEnvironment _hostingEnvironment;
        #endregion

        #region Constructors
        public CaptchaService(IMemoryCache cache, IHostingEnvironment hostingEnvironment)
        {
            _cache = cache;
            _hostingEnvironment = hostingEnvironment;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Get image captcha
        /// </summary>
        public CaptchaResult GetImageCaptcha(ImgCaptchaDto imgCaptchaDto)
        {
            var captchaCode = ImageCaptchaHelper.GenerateCaptchaCode();
            var result = ImageCaptchaHelper.GenerateCaptcha(100, 36, captchaCode);
            _cache.Set($"ImgCaptcha{imgCaptchaDto.ImgCaptchaType}{imgCaptchaDto.Mobile}", result.CaptchaCode);
            return result;
        }

        /// <summary>
        /// Validate image captcha
        /// </summary>
        public bool ValidateImageCaptcha(ImgCaptchaDto imgCaptchaDto)
        {
            var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{imgCaptchaDto.ImgCaptchaType}{imgCaptchaDto.Mobile}");
            if (string.Equals(imgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Get SMS captcha
        /// </summary>
        public (bool, string) GetMsgCaptcha(MsgCaptchaDto msgCaptchaDto)
        {
            if (string.IsNullOrWhiteSpace(msgCaptchaDto.ImgCaptcha))
                throw new BusinessException((int)ErrorCode.BadRequest, "Please enter image captcha");

            var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}");
            if (!string.Equals(msgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase))
                return (false, "Validation failed, please provide correct phone number and image captcha");

            string key = $"MsgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}";
            var cachedMsgCaptcha = _cache.Get<MsgCaptchaDto>(key);
            if (cachedMsgCaptcha != null)
            {
                var offsetSeconds = (DateTime.Now - cachedMsgCaptcha.CreateTime).Seconds;
                if (offsetSeconds < 60)
                    return (false, $"SMS requests are too frequent, please wait {60 - offsetSeconds} seconds");
            }

            var msgCaptcha = MsgCaptchaHelper.CreateRandomNumber(6);
            msgCaptchaDto.MsgCaptcha = msgCaptcha;
            msgCaptchaDto.CreateTime = DateTime.Now;
            msgCaptchaDto.ValidateCount = 0;
            _cache.Set(key, msgCaptchaDto, TimeSpan.FromMinutes(2));

            if (_hostingEnvironment.IsProduction())
            {
                // TODO: Call third‑party SDK to send SMS
                return (true, "Sent successfully");
            }
            else
            {
                return (true, $"Sent successfully, SMS code: {msgCaptcha}");
            }
        }

        /// <summary>
        /// Validate SMS captcha
        /// </summary>
        public (bool, string) ValidateMsgCaptcha(MsgCaptchaDto msgCaptchaDto)
        {
            var key = $"MsgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}";
            var cachedMsgCaptcha = _cache.Get<MsgCaptchaDto>(key);
            if (cachedMsgCaptcha == null)
                return (false, "SMS code invalid, please request a new one");

            if (cachedMsgCaptcha.ValidateCount >= 3)
            {
                _cache.Remove(key);
                return (false, "SMS code expired, please request a new one");
            }
            cachedMsgCaptcha.ValidateCount++;

            if (!string.Equals(cachedMsgCaptcha.MsgCaptcha, msgCaptchaDto.MsgCaptcha, StringComparison.OrdinalIgnoreCase))
                return (false, "Incorrect SMS code");
            else
                return (true, "Validation passed");
        }
        #endregion
    }
}

Before sending an SMS, the service requires a successful image‑captcha verification, typically a drag‑to‑match puzzle provided by ImageCaptchaHelper.GenerateCaptchaCode().

The implementation binds the generated image captcha to the phone number, ensuring that only requests with a correct image captcha can obtain an SMS code.

if (string.IsNullOrWhiteSpace(msgCaptchaDto.ImgCaptcha))
    throw new BusinessException((int)ErrorCode.BadRequest, "Please enter image captcha");

var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}");
if (!string.Equals(msgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase))
    return (false, "Validation failed, please provide correct phone number and image captcha");

The service also enforces a cooldown period for the same phone number, configurable via parameters, and limits the number of validation attempts (default three) before the SMS code expires.

if (cachedMsgCaptcha.ValidateCount >= 3)
{
    _cache.Remove(key);
    return (false, "SMS code expired, please request a new one");
}
cachedMsgCaptcha.ValidateCount++;

Overall, the project offers a clear, plug‑and‑play backend solution for generating, sending, and validating SMS verification codes, with extensible points for additional checks.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CSecurityCaptchaASP.NET CoreSMS Verification
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.