DEV Community

Cover image for The Unexpected Struggle of Implementing Custom CAPTCHA on Linux
vatana7
vatana7

Posted on

The Unexpected Struggle of Implementing Custom CAPTCHA on Linux

Last week, my boss assigned me a seemingly simple task: implement custom CAPTCHA validation for our internal Sales Portal login page. He specifically requested that we avoid Google’s ReCAPTCHA due to regional policies (or whatever reason he had). The requirements were straightforward:

  • Display a 300px × 76px CAPTCHA image with random text and distortion.
  • Provide a refresh button to regenerate the CAPTCHA.

Choosing the Right CAPTCHA Solution

I immediately jumped into research mode, looking for a solution. After some time, I stumbled upon CaptchaGen, a handy NuGet package that could generate distorted text images with minimal effort. It seemed like the perfect fit!

I quickly implemented it, ran some tests locally, and deployed it to our SIT (System Integration Testing) environment. Everything seemed fine—until I actually tried to use it.

The First Unexpected Issue: 401 Unauthorized

The first thing I noticed after deployment was that calling the CAPTCHA generation endpoint returned a 401 Unauthorized error.

Confused, I double-checked my controller—there was no [Authorize] attribute anywhere. So why was it rejecting unauthenticated requests?

After digging through the Jenkins logs, I found the real culprit:

The Hidden Dependency on System.Drawing

Turns out, CaptchaGen relies on System.Drawing, a package that is not supported on Linux—which was a problem because our AKS (Azure Kubernetes Service) environment runs on Linux containers.

Microsoft officially recommends avoiding System.Drawing in non-Windows environments starting from .NET 6 because it depends on GDI+, which is Windows-specific.

Switching to a Linux-Friendly Alternative

With that in mind, I had to look for an alternative. After some research, I found ImageSharp by SixLabors—a powerful image processing library that works across different platforms without relying on System.Drawing.

I refactored my implementation using ImageSharp, deployed it, and… hit another frustrating issue.

The Second Issue: Missing Arial Font on Linux

This time, the error said:

CreateFont() cannot find Arial font on the system.

My first reaction? Huh? Why wouldn’t Arial exist?

In my mind, Arial is a universal font—it should be available everywhere, right? Well, turns out, Linux doesn’t come with Arial pre-installed.

Here’s the problematic code:

ctx.DrawText(code, SystemFonts.CreateFont("Arial", fontSize), Color.Gray, new PointF(80, 20));
Enter fullscreen mode Exit fullscreen mode

Since Linux lacks Arial by default, SystemFonts.CreateFont("Arial", fontSize) simply failed to find the font, causing the error.

I tried manually installing Arial on AKS, but no luck. After hours of troubleshooting and frustration, I almost gave up—until I stumbled upon the missing piece of the puzzle.

The Solution: Embedding the Font in the Project

The problem was that I was using SystemFonts instead of loading a custom font file. The solution? Embed the Arial font inside the project and load it manually.

Following this official documentation, I fixed my code by explicitly loading the font from a file:

var fontCollection = new FontCollection();
var fontFamily = fontCollection.Add("path/to/arial.ttf");
var font = fontFamily.CreateFont(fontSize);
ctx.DrawText(code, font, Color.Gray, new PointF(80, 20));
Enter fullscreen mode Exit fullscreen mode

With this approach, the CAPTCHA finally worked flawlessly. 🎉

Lessons Learned

This seemingly simple task turned into an unexpected debugging marathon. But I walked away with a few valuable lessons:

  1. Read the documentation carefully. I wasted time due to my rookie mistake of overlooking compatibility issues.
  2. Linux-based environments handle fonts differently. Never assume a font exists—always package the required font with your app if you need it.
  3. Avoid System.Drawing in non-Windows environments. Libraries like ImageSharp are much safer and more portable.

At the end of the day, I got CAPTCHA working—but not without a battle. Hopefully, this post saves someone else the same headache. 😉

Top comments (0)