Intro
While implementing my new personal website, I added a Contact
page, on which people can send messages to me by filling a form.
This is a low-cost way to say something to the owner of the website. It can be anonymous, for instance, you can just type yo
in the message box and click Send
, then seconds later, I receive an email showing the message, and I have no idea who you are.
This feature looks convenient but also opens a window to everyone who wants to spam me. Someone may even willing to spend time writing a load-testing script for me (sending hundreds of thousands of messages to me within seconds to see if the website can handle it).
To prevent such case happens, I have to add a CAPTCHA
(c
ompletely a
utomated p
ublic T
uring test to tell c
omputers and h
umans a
part) field. As a result, it became hard for people to write a program to flood my inbox.
My homepage is a static page generated by Vue
, it requests external APIs through Javascript. Thus, the sending email service, or with the captcha service, is a standalone service running on my server.
Design
User Web Page Server Redis
| | | |
| |-----request key----->| |
| | generate key | (1)
| | |---save key---->|
| |<-----return key------| |
| |--request img by key->| |
| | generate code & img | (2)
| | |---save code--->|
| |<-----return img------| |
|---input code-->| | |
| |----send code & key-->| | (3)
| | |------key------>|
| | |<-----code------|
| | compare codes |
| | op(e.g. send email) |
| |<----return result----| |
(1) Cache Key
The first request initialized by the client is asking for a key
, which is used as the key in Redis as well as the argument used for asking for Captcha image in the following request.
The reason why the client has to send two separate requests to get the Captcha image is that the server is not able to return the key and the image data at the same time. This API is designed to be stateless, otherwise, you can manage a session instead of keeping an identifier like the key
value in this example.
In the following code snippet, we also remove the Captcha data from Redis by the key given in the request. This allows the user to refresh the captcha image and the server clears the previous captcha. Besides, each key will expires automatically (deleted from Redis) if it is not cleared by any request.
|
|
(2) Captcha Key-Image Pair
The captcha module is used here to generate the Captcha image.
|
|
(3) Verification
Nothing magical here, just compare the cached code with the user input. Take note that each captcha is one-time use only, it will be deleted no matter the user input is correct or not.
|
|
Conclusion
This is one possible way to implement a Captcha service, at least it can protect my mail inbox to a certain extent. In addition, there can be some improvements on it, for example, adding IP limitations, so that users won’t be able to initialize a huge amount of Captcha data in your Redis cache.
The full code example will be available at my GitHub soon.