Captcha using HTML 5 Canvas and JavaScript

Hi Guys!

It’s been a while since my last post.

In this post, I shall discuss the creation of a captcha code to secure web forms using the HTML 5 Canvas tag along with JavaScript and PHP as the server side language. I will load a custom web font using the JavaScript Font Face API and draw a random text string on the canvas with it. The random Captcha text will also be refreshable in case the site visitor does not find it legible enough.

I assume that the reader knows PHP Web Forms and Sessions, CSS, HTML5 Canvas JavaScript API, JavaScript for browsers including FontFace API and AJAX. You may read the appropriate tutorials from the web if you don’t.

How to create a random string

The following code creates a random text and initializes it into the PHP session.

<?php 
@session_start();

$_SESSION["captcha_hash"] = substr(hash('joaat', microtime()), 0, 6);

?>

As you can see, a timestamp is created with a call to the microtime() function. It is then hashed with the JOAAT algorithm. We then extract the first six characters of this hash as our captcha string.

Creating The Web Form

We will create the HTML form or web form using HTML and CSS for coding and styling. Here it is:

<!DOCTYPE html>
<html>
<head>
  <style type="text/css">
  #captcha_code{ width: 180px; }
  </style>
  <script src="https://code.jquery.com/jquery-3.6.4.min.js" integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous"></script>
</head>
<body>
  <h1>Canvas Captcha</h1>
  <?php if(isset($_SESSION["form_msg"])){ echo $_SESSION["form_msg"]; unset($_SESSION["form_msg"]); } ?>
  <img src="images/captcha.jpg" alt="captcha image" id="captcha_img" style="display:none;" />
  <form action="" method="POST">
  <canvas id="captcha" style="border: solid 1px black;" width="218" height="60" ></canvas>
  <img src="images/refresh.png" alt="refresh image" id="refresh_img" /><br />
  <input type="text" name="captcha_code" value="" id="captcha_code" maxlength="6" />
  <input type="submit" name="submit" value="Submit" />
  </form>
  <script src="script/js/script.js"></script>
</body>
</html>

As you can see, we declare an HTML 5 document with the DOCTYPE declaration. We fetch the latest jQuery file from
https://code.jquery.com/ in the script tag in the head of the HTML 5 document we just created.

We have created an invisible image fetching the image from the path “images/captcha.jpg” relative to our document
or web page. We have also used some CSS to style the text input of our web form.

Notice the call to fetch “script/js/script.js”. We will discuss this later.

The Script to which Our Form is Submitted

You will notice the script above the <form> web form tag above.
In this script, we will simply compare the value of the “captcha_code” form field (i.e. $_POST[“captcha_code”]) and the
random string stored in the PHP session (i.e. $_SESSION[“captcha_hash”]) when the form is submitted by the user.

The code is given below:

<?php 
  @session_start();

  if(isset($_POST["submit"]) && $_POST["submit"] == "Submit")
  {
    
    if($_POST["captcha_code"] != $_SESSION["captcha_hash"])
    {
      //failed
      $_SESSION["form_msg"] = "<span style='color:red;'>Security challenge failed!</span>";
      
      header("Location: index.php");
      exit;
    }
    else{
      //passed
      $_SESSION["form_msg"] = "<span style='color:green;'>Security challenge passed!</span>";
      
      header("Location: index.php");
      exit;
    }
  }
  else{
    $_SESSION["captcha_hash"] = substr(hash('joaat', microtime()), 0, 6);
  }

?>

The Script Which Renders the Captcha String

Perhaps you noticed the call to fetch “script/js/script.js”.

Why do we need to do this?

This is needed because we have to reference the PHP session variable $_SESSION[“captcha_hash”], so that we can render it on the HTML 5 canvas using JavaScript.

When the web page loads for the first time, the following code renders the captcha:

var canvasObj = document.getElementById('captcha');
// we need this to load the font
var myFont = new FontFace('myFont', 'url(font/Moms_typewriter.ttf)');

myFont.load().then(function(font){

  // with canvas, if this is ommited won't work
  document.fonts.add(font);

  console.log('Font loaded');

  console.log(canvasObj);
      
  var captchaImage = document.getElementById("captcha_img");
  
  var context = canvasObj.getContext("2d");
      
  context.drawImage(captchaImage,0,0);
        
  context.font='28px myFont';
      
  var capText = '';
      
  jQuery.get("script/getcaptchastring.php", {'action':'fetch'}, function(response){
        
    capText = response;
        
    var capArray = capText.split('');
      
    drawLines(context);
        
    rotateText(context, capArray);
        
        
  });
      
});

 

As you can see, there are two JavaScript functions named “drawLines” and “rotateText”, which need elaboration.

The “drawLines” function draws zigzag lines in the captcha image background by obtaining a 2D graphics context of the canvas object using the line of JavaScript as follows:

var context = canvasObj.getContext("2d");

This complete JavaScript function is as follows:

function drawLines(context)
{
  //random lines
  context.moveTo(0,0);
  context.lineTo(80, 60);
  context.stroke();
  
  context.moveTo(50,0);
  context.lineTo(20, 60);
  context.stroke();
  
  context.moveTo(58, 60);
  context.lineTo(100, 0);
  context.stroke();
  
  context.moveTo(110, 0);
  context.lineTo(140, 60);
  context.stroke();
  
  context.moveTo(120, 60);
  context.lineTo(190, 0);
  context.stroke();
}

The “rotateText” function renders the six characters of the split string, which was previously set in PHP session. It rotates the canvas 2D graphical context this way and that, sumultaneously rendering the individual characters of the captcha in the custom font. After finishing its job, the context is restored to its initial orientation.

function rotateText(context, capArray)
  {
    //rotated captcha
    
    context.rotate(10 * Math.PI / 180);
    context.fillText(capArray[0], 35, 40);
    
    context.rotate(5 * Math.PI / 180);
    context.fillText(capArray[1], 59, 35);
    
    context.rotate(-5 * Math.PI / 180);
    context.fillText(capArray[2], 79, 30);
    
    context.rotate(-10 * Math.PI / 180);
    context.fillText(capArray[3], 99, 30);
    
    context.rotate(10 * Math.PI / 180);
    context.fillText(capArray[4], 119, 30);
    
    context.rotate(-5 * Math.PI / 180);
    context.fillText(capArray[5], 134, 25);
    
    context.rotate(-5 * Math.PI / 180);
    context.restore();
  } 

Creating a Refreshable Captcha

In the code listing that creates our web form, notice the following line:

<img src="images/refresh.png" alt="refresh image" id="refresh_img" />

We will add a click handler to it so that captcha can be refreshed on click. We will use AJAX so as to get the PHP session variable from the server side, modify it and render it without reloading the entire page. Refreshing will change the session variable and hence the captcha string, but the logic to render the changed string remains the same.

Note that on each refresh, we need to clear the Canvas of the zigzag lines and the wavy text, so that there is no overwriting and spoiling the appearance of the captcha.

Here is the code:

//Client side code

  jQuery(document).ready(function()
  {	
    var canvasObj = document.getElementById('captcha'); //canvas object
    
    var captchaImage = document.getElementById("captcha_img"); //background image
    
    var context = canvasObj.getContext("2d");
    
    //click event handler
    jQuery("#refresh_img").click(function(){
    
      //AJAX GET call
      jQuery.get("script/getcaptchastring.php", {'action':'refresh'}, function(response){
        
        var capArray = response.split('');
        
        context.clearRect(0, 0, canvasObj.width, canvasObj.height); //wipe the canvas clean on each refresh 
        
        context.drawImage(captchaImage,0,0);
        
        context.font='28px myFont';
        
        drawLines(context);
        
        rotateText(context, capArray);	
        
      });
    });
  });

There is an AJAX call to “script/getcaptchastring.php” which modifies the captcha string. The following is the code listing for getcaptchastring.php:

<?php 
@session_start();

if(isset($_GET['action']))
{
  if($_GET['action'] == 'fetch')
  {
    echo $_SESSION["captcha_hash"];

    exit();
  }
  elseif($_GET['action'] == 'refresh')
  {
    $captcha_hash_new = substr(hash('joaat', microtime()), 0, 6);

    $_SESSION["captcha_hash"] = $captcha_hash_new;

    echo $_SESSION["captcha_hash"];

    exit();
  }
}

?>

Conclusion

So all in all, we did the following:

  • Initialized the first six characters of the hash of microtime() as a session variable in PHP
  • Created a HTML 5 page with a web form and Canvas tag.
  • Created a PHP script to which the form will be submitted, to check the captcha challenge.
  • Created a script to render the Captcha string on the background image, with zigzag lines, with wavy characters using a custom font.
  • Created Server side and client side logic to modify the session variable, return it to the client side and render it to the captcha background each time refresh image is clicked and the canvas is cleared.

I hope you found it useful, since we often need to secure web forms with some kind of captcha. The code is found here. The demo is here.