Skip to content

Best Practices: Security

When to Use

Every AJAX implementation requires security considerations.

Critical Security Measures

Critical Security Measures:

  1. Access Control
  2. Every AJAX callback and route needs _permission or _custom_access
  3. Check triggering element accessibility in Form API callbacks
  4. Never trust client-sent data; validate server-side

  5. Input Validation

  6. Validate and sanitize all user input
  7. Use FormStateInterface validation, not JavaScript-only
  8. Apply upload validators for file uploads (extension, size, MIME type)

  9. CSRF Protection

  10. Drupal handles CSRF for Form API automatically
  11. Add _csrf_request_header_token: 'TRUE' to custom POST AJAX routes (validates the X-CSRF-Token header Drupal AJAX sends automatically; _csrf_token: 'TRUE' validates a URL query param instead — see CSRF Protection)
  12. Verify $request->isXmlHttpRequest() to prevent direct calls

  13. XSS Prevention

  14. Always return render arrays, not HTML strings
  15. Use Html::escape() for user input in markup
  16. MessageCommand and AnnounceCommand auto-escape content
  17. Never use '#markup' => $user_input without sanitization

  18. SQL Injection Prevention

  19. Use Entity Query API, not direct database queries
  20. Never concatenate user input into queries
  21. Use parameterized queries for custom database operations

  22. Content Security Policy (CSP)

  23. Avoid inline JavaScript in AJAX responses
  24. Use attached libraries instead of inline scripts
  25. Configure CSP headers in settings.php

Common Vulnerabilities

Common Vulnerabilities:

Vulnerability Attack Vector Prevention
XSS Unsanitized user input in AJAX response Use render arrays, escape manually if needed
CSRF Forged requests to AJAX endpoints Form API handles automatically; add _csrf_request_header_token: 'TRUE' on custom POST AJAX routes
Unauthorized access Missing permission checks Add _permission or _custom_access
SQL injection User input in queries Use Entity Query API
File upload attacks Malicious file uploads Configure upload validators

Pattern

// 1. Access control on every route
my_module.ajax_endpoint:
  requirements:
    _permission: 'access content'
    _csrf_request_header_token: 'TRUE'   // POST AJAX route: validates X-CSRF-Token header

// 2. Return render arrays, not HTML strings
// BAD:
return '<div>' . $user_input . '</div>';

// GOOD:
return [
  '#markup' => $this->t('@content', ['@content' => $user_input]),
];

// 3. Validate upload files
$form['file'] = [
  '#type' => 'managed_file',
  '#upload_validators' => [
    'FileExtension' => ['extensions' => 'jpg jpeg png'],
    'FileSizeLimit' => ['fileLimit' => '2M'],
  ],
];

// 4. Verify triggering element
$triggering_element = $form_state->getTriggeringElement();
if (!$triggering_element) {
  throw new AccessDeniedHttpException();
}

// 5. Use Entity Query for database operations
$nids = $this->entityTypeManager->getStorage('node')->getQuery()
  ->condition('type', 'article')
  ->condition('field_value', $user_input)  // Safe: parameterized
  ->accessCheck(TRUE)
  ->execute();

See Also