tokencaptcha

package module
v1.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2025 License: MIT Imports: 21 Imported by: 0

README

token-captcha — Stateless CAPTCHA generator and verifier for Go

Hero Image

A lightweight stateless captcha generator and verifier written in Go.
It allows frontend applications to request captchas through an API endpoint
without any persistent storage or session handling on the server side.


🚀 Installation

go get github.com/yousef-muc/[email protected]

Go Reference

Keywords: go captcha, stateless captcha, hmac token, image verification, ttf font captcha

🧠 Overview

token-captcha provides a stateless captcha mechanism that uses HMAC-SHA256
to generate and validate captcha tokens without storing any data on the server.

Each token contains:

  • A random nonce
  • An expiry timestamp
  • An action name (to scope usage)
  • An HMAC signature based on a secret key

Because the validation uses only the token and user’s answer,
any server instance can verify a captcha independently — perfect for distributed systems
and microservices where shared state is undesirable.


⚙️ Features

  • ✅ Stateless verification (no sessions or DB)
  • 🧩 Configurable captcha length, font, size, and noise
  • 🖼️ Built-in PNG generation with embedded TTF fonts
  • 🔐 HMAC-based integrity and authenticity
  • ⏱️ Expiry enforcement and action binding
  • 💡 Human-friendly characters (no I, O, 1, 0)

Noto Sans Jetbrain Mono

🖥️ Example: Server-side Usage (Go)

Below is a minimal REST API example that issues and verifies captchas.

package main

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/yousef-muc/token-captcha"
)

func main() {
  // Initialize a new TokenCaptcha service with custom configuration.
	// The secret key is used for HMAC signing, ensuring that issued tokens
	// cannot be tampered with. The captcha length, expiry duration, and
	// rendering options (such as image output and font selection) define
	// how the captcha will be generated and validated.
	svc := tokencaptcha.New(tokencaptcha.Config{
		Secret:        []byte("CHANGE-ME"), // replace with a secure random key in production
		Length:        6,                   // number of captcha characters
		Expiry:        2 * time.Minute,     // token lifetime
		Image:         true,                // enable PNG image generation
    Width:         220,                 // image width in pixels
    Height:        70,                  // image height in pixels
		CaseSensitive: false,               // ignore case during verification
		Font: tokencaptcha.FontConfig{
			Name: "noto-sans", // built-in font name
			Size: 32,          // font size in points
			DPI:  96,          // rendering DPI
		},
	})

	// Endpoint: /captcha/issue
	// Generates and returns a new captcha token, optionally including
	// a base64-encoded PNG image. The "action" parameter in the query
	// string can be used to scope captcha usage to a specific form or API.
	http.HandleFunc("/captcha/issue", func(w http.ResponseWriter, r *http.Request) {
		action := r.URL.Query().Get("action") // optional context for the captcha
		cap, err := svc.IssueCaptcha(action)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		// Respond with JSON containing the captcha token and image (if enabled)
		json.NewEncoder(w).Encode(cap)
	})

	// Endpoint: /captcha/verify
	// Accepts a JSON payload with the issued token, the user's answer,
	// and the expected action. The captcha is verified using HMAC
	// validation and expiry checks without any server-side state.
	http.HandleFunc("/captcha/verify", func(w http.ResponseWriter, r *http.Request) {
		var in struct {
			Token  string `json:"token"`  // base64-encoded token returned by /captcha/issue
			Answer string `json:"answer"` // user-provided captcha text
			Action string `json:"action"` // optional action for contextual validation
		}
		// Decode the request body and validate input
		if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		// Verify the captcha; returns true if valid and not expired
		valid := svc.Verify(in.Token, in.Answer, in.Action)
		json.NewEncoder(w).Encode(map[string]any{"valid": valid})
	})

	// Start the HTTP server on port 8080.
	// The API now serves both /captcha/issue and /captcha/verify endpoints.
	http.ListenAndServe(":8080", nil)
}

🌐 Example: Client-side Usage

You can display the captcha image and send user input to the server for verification.

Request a captcha
async function loadCaptcha() {
  const res = await fetch('/captcha/issue?action=signup');
  const data = await res.json();
  document.querySelector('#captcha-img').src = 'data:image/png;base64,' + data.PNGB64;
  window.captchaToken = data.TokenB64;
}
Submit and verify
async function verifyCaptcha() {
  const answer = document.querySelector('#captcha-answer').value;
  const body = JSON.stringify({
    token: window.captchaToken,
    answer,
    action: 'signup',
  });
  const res = await fetch('/captcha/verify', { method: 'POST', body });
  const data = await res.json();
  alert(data.valid ? 'Captcha passed!' : 'Invalid captcha, try again.');
}
Example HTML
<img id="captcha-img" alt="Captcha Image" />
<input id="captcha-answer" placeholder="Enter captcha" />
<button onclick="verifyCaptcha()">Verify</button>

⚙️ Configuration Reference

Field Type Description
Secret []byte Secret key for HMAC signing
Length int Number of characters in captcha text
Expiry time.Duration Token lifetime before expiration
Image bool Whether to generate a PNG image
Width / Height uint16 Dimensions of captcha image
Noise uint16 Number of random noise lines
CaseSensitive bool Whether user answers are case-sensitive
AllowActions []string Optional list of allowed action names
FG / BG color.Color Foreground and background colors
Font FontConfig Font selection and scaling options
FontConfig
Field Type Description
Name string Built-in font name (noto-sans, jetbrains-mono)
TTF []byte Optional custom font data
Size float64 Font size in points
DPI float64 Dots per inch for rendering

🔒 Security Notes

  • Always set a unique secret key via Config.Secret.
  • Tokens are signed using HMAC-SHA256, ensuring integrity and authenticity.
  • Expiry timestamps prevent replay attacks.
  • Captcha answers are compared in constant time to avoid timing leaks.

🧩 Why Stateless?

Traditional captchas require storing challenge–response pairs on the server.
This library embeds all required data (nonce, expiry, action, HMAC) in the token itself.
Verification can therefore be done by any server instance without shared state or a database.


🧾 License

MIT License

Copyright (c) 2025 yousef-muc

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config added in v0.2.1

type Config struct {
	Secret        []byte        // secret key used for HMAC token signing
	Length        int           // number of characters in the captcha answer
	Expiry        time.Duration // lifetime of a captcha token before it expires
	Image         bool          // whether to generate a PNG image for the captcha
	Width         uint16        // image width in pixels
	Height        uint16        // image height in pixels
	Noise         uint16        // number of random noise lines drawn on the image
	CaseSensitive bool          // whether captcha answers are case-sensitive
	AllowActions  []string      // optional list of allowed action identifiers
	FG            color.Color   // foreground color for text
	BG            color.Color   // background color of the captcha image
	Font          FontConfig    // font configuration for text rendering
}

Config defines all configurable parameters for the TokenCaptcha service. It controls how tokens are generated, verified, and optionally rendered as images. Missing or zero values are automatically normalized to defaults when creating a new Service instance using New(Config).

type FontConfig added in v0.2.1

type FontConfig struct {
	Name string  // name of the built-in font (e.g. "noto-sans" or "jetbrains-mono")
	TTF  []byte  // optional raw TTF font data for custom fonts
	Size float64 // font size in points
	DPI  float64 // dots per inch used for text rendering
}

FontConfig defines the font settings used when rendering captcha images. It allows either selecting a built-in font by name or providing a custom TrueType font as raw byte data. The Size and DPI fields control text scaling and resolution during rendering.

type IssueResult added in v1.0.0

type IssueResult struct {
	TokenB64 string // Base64URL JSON tokenPayload
	PNGB64   string // optional field, only set if cfg.Image is true
}

IssueResult represents the response of a generated captcha. TokenB64 contains the Base64URL-encoded JSON token payload. PNGB64 contains the optional captcha image in Base64 PNG format if image generation is enabled.

type Service added in v0.2.1

type Service struct {
	// contains filtered or unexported fields
}

Service represents the main captcha generator and verifier. It operates in a stateless manner, meaning that no data needs to be stored on the server. The service uses an HMAC signature to verify captcha tokens without maintaining sessions.

func New

func New(cfg Config) *Service

New creates a new captcha service using the provided configuration. It automatically normalizes the configuration by filling in missing or invalid values with secure and reasonable defaults before returning the initialized Service instance.

func (*Service) IssueCaptcha added in v1.0.0

func (s *Service) IssueCaptcha(action string) (IssueResult, error)

IssueCaptcha creates a new stateless captcha token and optionally an image. It generates a random answer, computes the HMAC signature, encodes the payload, and returns the token along with the image if configured. The returned captcha is completely stateless and can be verified later using the Verify method without any stored session data.

func (*Service) Verify added in v1.0.0

func (s *Service) Verify(tokenB64, userAnswer, expectedAction string) bool

Verify validates a user provided captcha answer against a given token. It decodes the token, checks expiration and action values, recalculates the expected HMAC signature, and performs a constant-time comparison. Returns true if the captcha is valid and false otherwise.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL