Content Validation
Once incoming data has been deserialized, how and when do you ensure it's valid? And if you determine it's invalid, how do you report that information?
Taking a layered security approach, the sooner you can deliver validation errors, the better. Denial of Service attacks will often send invalid data in order to mire the system in long-running and processor intensive requests, thus denying service to valid requests.
Types of Validation
You know you need to validate your requests; the question is: how? With a typical HTML web
application, you have forms, and server-side logic for validating the forms. What tools do you have
for APIs, where the data is usually not of the application/x-www-form-urlencoded
media type?
One option is JSON Schema, which is both a way to describe the data format, as well as validate it. This approach requires having tools server-side for transforming the schema into validation rules that you can run against your code.
Another option is to treat the incoming data as form data; deserialize it into an array, and pass it
to the same logic you would use to validate a form. This requires that your form validation logic
does not operate directly on $_POST
or $_GET
, but instead allows passing the data set to
validate.
Zend Framework 2 offers an approach similar to the latter, via the
Zend\InputFilter
component. This component allows you to describe and validate data sets of arbitrary complexity.
Additionally, it allows for the ability to both set custom error messages as well as retrieve
validation error messages in a structured format. Apigility's
zf-content-validation module provides
functionality for mapping Zend Framework 2 input filters to services, and utilizes API
Problem in order to return validation error messages to
the end-user of the API.
If the data provided does not overlap with the set described by the input filter, Apigility will
return a 400 Bad Request
status code. If any portion of the data set does overlap, but is
invalid, instead a 422 Unprocessable Entity
status will be returned with an
application/problem+json
payload that contains a validation_messages
key.
As an example, consider a "Status" service that accepts two fields, "message" and "user"; the first cannot be empty, and must be less than or equal to 140 characters; the second will be validated against a regular expression of valid users. Let's consider a request that provides an empty message and an invalid user:
POST /status HTTP/1.1
Accept: application/vnd.status.v2+json
Content-Type: application/json
{
"message": " ",
"user": "matthew"
}
Apigility will deserialize the data and pass it to the configured input filter, which will then determine that the data is invalid. The following response will be provided:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"detail": "Failed Validation",
"status": 422,
"title": "Unprocessable Entity",
"type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
"validation_messages": {
"message": {
"isEmpty": "Value is required and can't be empty"
},
"user": {
"regexNotMatch": "Invalid user supplied."
}
}
}
Validation errors from Apigility will always follow this format, thereby providing predictability to consumers of your APIs.
HTTP Method-Specific Validation
Sometimes the validation rules for a given URI may change based on which HTTP method is being used.
As an example, during creation of a user, via POST
, you may want to specify just a name and email.
However, during a later operation to update a password via PATCH
, you may be able to receive only
the password. An operation that replaces all details of the user via PUT
may need to validate each
and every field representing the user.
The zf-content-validation
module provides granularity beyond just mapping input filters to
services; it also allows you to map input filters to specific HTTP methods for a given service. In
the case of REST services, it also differentiates between
collection and entity URIs, allowing an input filter for each HTTP method for each.
Summary
Zend Framework 2 provides the ability to short-circuit the request lifecycle at any point by returning a "response" object. Apigility leverages this fact by registering an event listener after content negotiation completes, but before the service itself executes, ensuring we intercept validation errors early.
Read the content validation chapter for more details.