Captcha API Basics
Liferay provides a headless API to retrieve and submit captchas using the SimpleCAPTCHA engine. Using the /captcha endpoint from the API Explorer, you can add captchas in your custom implementations without using a tag library. There are two endpoints:
/GET- Retrieve a Base64 encoded captcha image string and a JWT token for validation/POST- Send the answer of the captcha along with the JWT token for verification
Getting a Captcha Challenge
Start a new Liferay DXP instance by running
docker run -it -m 8g -p 8080:8080
Sign in to Liferay at http://localhost:8080 using the email address test@liferay.com and the password test. When prompted, change the password to learn.
Once Liferay is running,
-
Download and unzip Captcha API Basics.
curl https://resources.learn.liferay.com/dxp/latest/en/installation-and-upgrades/securing-liferay/developer-guide/liferay-p6s7.zip -Ounzip liferay-p6s7.zip -
Use the cURL script to retrieve a captcha image string and a validation token. On the command line, navigate to the
curlfolder. Execute theCaptcha_GET_FromInstance.shscript../Captcha_GET_FromInstance.shThe JSON response shows the captcha image string and the token:
{ "image" : "data:image/png;base64,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", "token" : "BnDK5SupcZFFKqlBiswtjHv0tw6ptDYoICH8Y/wccQAwmJzS+pvjBxSiPkDaEwnDcuvHCWuHb4slvrdSZVy3W5N2EDNbDRjljs9ksftAkp8s3Fa6bKYiu4hYsCCCCwJA" } -
Copy the value of the image field without the
data:image/png;base64,prefix and run the following command:echo <COPIED_IMAGE_STRING> | base64 -d > captcha.pngThis saves the captcha image in the current directory with the filename
captcha.png. Open the file to view the captcha.
-
From the previous JSON response, copy the value of the
tokenfield. You’ll use this token to validate the captcha through the/POSTendpoint. -
Alternatively, call the REST service using the Java client. Navigate into the
javafolder and compile the source files:javac -classpath .:* *.java -
Run the
Captcha_GET_FromInstanceclass.java -classpath .:* Captcha_GET_FromInstance
Examine the cURL Command
The Captcha_GET_FromInstance.sh script calls the REST service with a cURL command.
curl \
"http://localhost:8080/o/captcha/v1.0/captcha/challenge" \
--user "test@liferay.com:learn"
Here are the command’s arguments:
| Arguments | Description |
|---|---|
"http://localhost:8080/o/captcha/v1.0/captcha/challenge" | Specify the REST service endpoint. |
--user "test@liferay.com:learn" | Enter basic authentication credentials. |
Basic authentication is used here for demonstration purposes. For production, you should authorize users via OAuth2. See Using OAuth2 to Authorize Users for a sample React application using OAuth2.
Examine the Java Class
The Captcha_GET_FromInstance.java class retrieves a captcha string and token by calling the CaptchaResource service.
public static void main(String[] args) throws Exception {
CaptchaResource.Builder builder = CaptchaResource.builder();
CaptchaResource captchaResource = builder.authentication(
"test@liferay.com", "learn"
).build();
Captcha captcha = captchaResource.getCaptchaChallenge();
System.out.println("Token: " + captcha.getToken());
byte[] imageBytes = Base64.getDecoder(
).decode(
captcha.getImage(
).split(
","
)[1]
);
try (FileOutputStream fileOutputStream = new FileOutputStream(
"captcha.png")) {
fileOutputStream.write(imageBytes);
}
}
}
This class invokes the REST service using only three lines of code:
| Line (abbreviated) | Description |
|---|---|
CaptchaResource.Builder builder = ... | Get a Builder for generating a CaptchaResource service instance. |
CaptchaResource captchaResource = builder.authentication(...).build(); | Use basic authentication and generate a CaptchaResource service instance. |
captchaResource.getCaptchaChallenge(); | Call the captchaResource.getCaptchaChallenge method. |
After retrieving the response, the token is displayed, and the Base64 class is used to decode the image string into bytes. This gets saved as an image in the same directory.
Note that the project includes the com.liferay.captcha.rest.client.jar file as a dependency. You can find client JAR dependency information for all REST applications in the API explorer in your installation at /o/api (e.g., http://localhost:8080/o/api).
The main method’s comment demonstrates running the class.
See CaptchaResource for service details.
Post Captcha Response
After retrieving the captcha image, you can use the /POST endpoint to submit your answer and token with a cURL or Java command. Replace abcd with your answer and efgh with your token.
Examine the Captcha_POST_ToInstance cURL Command
Command:
./Captcha_POST_ToInstance.sh abcd efgh
Code:
curl \
"http://localhost:8080/o/captcha/v1.0/captcha/response" \
--data-raw '
{
"answer": "'${1}'",
"token": "'${2}'"
}' \
--header "Content-Type: application/json" \
--request "POST" \
--user "test@liferay.com:learn" \
The Captcha_POST_ToInstance.sh script calls the REST service with a cURL command.
Here are the command’s arguments:
| Arguments | Description |
|---|---|
"http://localhost:8080/o/captcha/v1.0/captcha/response" | Specify the REST service endpoint. |
--data-raw '{ "answer": "'${1}'", "token": "'${2}'"}' | Enter the data to post. |
--header "Content-Type: application/json" | Set the request body format to JSON. |
--request "POST" | Set the HTTP method to invoke at the specified endpoint. |
--user "test@liferay.com:learn" | Enter basic authentication credentials. |
The API returns returns a 204 if the answer is valid. The JWT token has a nonce tracked by the /POST endpoint. You cannot reuse the same token if you’ve already sent the wrong answer.
Examine the Captcha_POST_ToInstance class
Command:
java -classpath .:* -Danswer=abcd -Dtoken=efgh Captcha_POST_ToInstance
Code:
public static void main(String[] args) throws Exception {
CaptchaResource.Builder builder = CaptchaResource.builder();
CaptchaResource captchaResource = builder.authentication(
"test@liferay.com", "learn"
).build();
captchaResource.postCaptchaResponse(
new Captcha() {
{
answer = String.valueOf(System.getProperty("answer"));
token = String.valueOf(System.getProperty("token"));
}
});
}
The Captcha_POST_ToInstance.java class sends the captcha answer along with a token by calling the CaptchaResource service.
This class invokes the REST service using only three lines of code:
| Line (abbreviated) | Description |
|---|---|
CaptchaResource.Builder builder = ... | Get a Builder for generating a CaptchaResource service instance. |
CaptchaResource captchaResource = builder.authentication(...).build(); | Use basic authentication and generate a CaptchaResource service instance. |
captchaResource.postCaptchaResponse(...); | Call the captchaResource.postCaptchaResponse method. |
The postCaptchaResponse() method accepts an instance of the Captcha class with the captcha answer and the JWT token. The API returns a 204 if the answer is valid. The JWT token has a nonce tracked by the /POST endpoint. You cannot reuse the same token if you’ve already sent the wrong answer.
Sending an Incorrect Captcha or Using an Invalid/Expired Token
If you send an incorrect captcha answer with the correct token, you receive the following response:
{
"status" : "BAD_REQUEST",
"title" : "Answer is invalid",
"type" : "CaptchaTextException"
}
If you send the right answer with the same token that was already used, you receive the following response:
{
"status" : "BAD_REQUEST",
"title" : "Token: BnDK5SupcZFFKqlBiswtjHv0tw6ptDYoICH8Y/wccQAwmJzS+pvjBxSiPkDaEwnDcuvHCWuHb4slvrdSZVy3W5N2EDNbDRjljs9ksftAkp8s3Fa6bKYiu4hYsCCCCwJA",
"type" : "IllegalArgumentException"
}
You get the same response for trying to use a token that’s expired.
The API Explorer shows the Captcha services and schemas and has an interface to test each service.