L’autenticazione REST API WordPress è il punto dove la maggior parte degli sviluppatori sbaglia. Mettono le credenziali in chiaro nel frontend, usano l’utente admin per chiamate API quotidiane, o peggio disabilitano del tutto l’autenticazione perché “tanto è solo per uso interno”. Nella nostra esperienza su 50+ siti gestiti con AgencyPilot, abbiamo visto di tutto: chiavi API commitate su Git, password admin in file JavaScript accessibili dal browser, endpoint protetti solo da obscurity. In questa guida vediamo i tre metodi di autenticazione REST API WordPress che funzionano nel 2026, quando usare ciascuno, e come configurarli con codice reale testato in produzione.
Se già conosci gli endpoint personalizzati REST API, questo articolo è il complemento naturale: non basta sapere come creare un endpoint, devi sapere come proteggerlo.
TL;DR
- Application Passwords è il metodo nativo più semplice: attivo di default da WordPress 5.6, ideale per integrazioni server-to-server
- JWT (JSON Web Token) è la scelta migliore per SPA e app mobile: stateless, scalabile, non richiede sessioni PHP
- OAuth 2.0 è overkill per la maggior parte dei progetti WordPress, ma necessario se integri con servizi di terze parti che lo richiedono
- Mai usare l’utente admin per chiamate API: crea un utente dedicato con capability minime
- Basic Auth senza HTTPS è un disastro di sicurezza: sempre abilitare TLS prima di qualsiasi autenticazione API
- Restringe gli endpoint con
permission_callback: non affidarti solo all’autenticazione, controlla anche le capability
I Metodi di Autenticazione REST API WordPress: Panoramica
WordPress supporta nativamente quattro metodi di autenticazione per la REST API. Non sono equivalenti e non sono intercambiabili. Scegliere il metodo sbagliato significa o avere un’architettura insicura o complicarsi la vita inutilmente.
| Metodo | Stateless | Setup | Best For | Rischio |
|---|---|---|---|---|
| Cookie Auth | No | Nativo | Frontend dello stesso sito | CSRF se non gestito |
| Application Passwords | Sì | Nativo (WP 5.6+) | Server-to-server, script | Password in chiaro nel header |
| JWT | Sì | Plugin richiesto | SPA, app mobile | Token rubato = accesso completo |
| OAuth 2.0 | Sì | Plugin + setup complesso | Integrazioni terze parti | Overkill per progetti semplici |
Entriamo nei dettagli di ciascun metodo, con configurazione e codice reale.
Application Passwords: Il Metodo Nativo Più Semplice
Le Application Passwords sono la risposta di WordPress al problema delle credenziali API. Introdotte in WordPress 5.6 (dicembre 2020), generano password dedicate per ogni integrazione API. Ogni password è indipendente dall’account utente principale: puoi revocarla senza cambiare la password dell’utente, e non funziona per il login classico.
Il funzionamento è banale. L’autenticazione avviene via Basic Auth, ma invece della password dell’utente usi una application password generata dal profilo WordPress.
Come generare una Application Password
- Vai in Utenti → Profilo nel backend WordPress
- Scorri fino alla sezione “Application Passwords”
- Inserisci un nome descrittivo (es. “AgencyPilot API Sync”)
- Clicca “Add New Application Password”
- Copia la password generata (non la rivedrai più)
Da quel momento, tutte le chiamate API con quella coppia utente + application password sono autenticate come quell’utente WordPress, con tutte le sue capability.
Esempio: chiamata API con Application Password
# Genera una application password dal profilo WordPress prima di usare questo script
WP_USER="api-sync-user"
WP_APP_PASS="xxxx xxxx xxxx xxxx xxxx xxxx"
curl -s -X GET "https://tuosito.it/wp-json/wp/v2/posts?per_page=5" \
-u "${WP_USER}:${WP_APP_PASS// /}" \
-H "Content-Type: application/json"
Nota: le application password contengono spazi per leggibilità, ma nelle chiamate HTTP vanno rimossi. Il ${WP_APP_PASS// /} in bash rimuove tutti gli spazi.
Creare un endpoint protetto con Application Passwords
Dal lato server, non devi fare nulla di speciale. Le Application Passwords sono gestite dal core di WordPress. Devi solo definire una permission_callback appropriata nel tuo endpoint:
add_action('rest_api_init', function() {
register_rest_route('agencypilot/v1', '/site-stats', [
'methods' => 'GET',
'permission_callback' => function($request) {
// Richiede un utente autenticato con capability di lettura
if (!current_user_can('read')) {
return new WP_Error(
'rest_forbidden',
'Non hai i permessi per accedere a questo endpoint',
['status' => 403]
);
}
return true;
},
'callback' => 'agencypilot_get_site_stats',
]);
});
function agencypilot_get_site_stats($request) {
return [
'posts_count' => wp_count_posts()->publish,
'pages_count' => wp_count_posts('page')->publish,
'users_count' => count_users()['total_users'],
'plugins_active' => count(get_option('active_plugins')),
'wp_version' => get_bloginfo('version'),
'php_version' => phpversion(),
];
}
La permission_callback è obbligatoria dalla versione 5.5 di WordPress. Se la ometti, WordPress restituisce un errore. Mai usare __return_true come permission_callback su endpoint che espongono dati sensibili.
Quando usare Application Passwords
- Script server-side che parlano con la REST API
- Integrazioni tra tuoi servizi (es. AgencyPilot che sincronizza post)
- Webhook in entrata da servizi esterni
- Qualsiasi scenario dove il client è trusted e la password può essere memorizzata lato server
Limiti delle Application Passwords
- La password viaggia in chiaro nell’header Authorization (Base64 non è crittografia). HTTPS è obbligatorio.
- Nessun meccanismo di scadenza automatica. Devi revocare manualmente.
- Non adatte per frontend pubblico: chiunque legga il codice JavaScript ha le credenziali.
- Ogni application password ha le stesse capability dell’utente. Non puoi limitare per scope.
JWT: Autenticazione Stateless per SPA e Mobile
Il JWT (JSON Web Token) è il standard de facto per l’autenticazione API stateless. A differenza delle sessioni PHP, un JWT è un token firmato che contiene tutte le informazioni necessarie per verificare l’identità dell’utente senza consultare il database a ogni richiesta. Per le agenzie che costruiscono frontend React o app mobile sopra WordPress, JWT è spesso la scelta migliore.
Come funziona JWT con WordPress
Il flusso è semplice:
- Il client invia username e password a un endpoint
/jwt-auth/v1/token - WordPress verifica le credenziali e restituisce un JWT firmato
- Il client memorizza il token (localStorage, cookie, o in-memory)
- Per ogni chiamata API successiva, il client invia il token nell’header
Authorization: Bearer <token> - Un middleware WordPress verifica la firma del token e autentica l’utente
Il token ha una scadenza configurabile. Dopo la scadenza, il client deve autenticarsi di nuovo o usare un refresh token.
Installazione del plugin JWT Auth
WordPress non supporta JWT nativamente. Serve un plugin. Il più usato è “JWT Authentication for WP REST API” di Useful Team:
# Installa via WP-CLI
wp plugin install jwt-authentication-for-wp-rest-api --activate
# Oppure scarica manualmente da:
# https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/
Configurazione del secret key
Il JWT viene firmato con una secret key. Devi definirla nel wp-config.php:
// wp-config.php — aggiungi prima di "/* That's all, stop editing! */"
define('JWT_AUTH_SECRET_KEY', 'una-stringa-casuale-di-almeno-64-caratteri');
define('JWT_AUTH_CREDENTIAL_SECRET', 'un-altra-stringa-casuale-di-64-caratteri');
// Genera con: openssl rand -base64 64
Usa openssl rand -base64 64 dal terminale per generare chiavi casuali. Mai riutilizzare la stessa chiave tra ambienti diversi (staging, produzione, development).
Endpoint JWT disponibili
Dopo l’attivazione del plugin, hai due endpoint:
# 1. Ottieni un token
curl -s -X POST "https://tuosito.it/wp-json/jwt-auth/v1/token" \
-H "Content-Type: application/json" \
-d '{"username":"api-user","password":"secret-pass-123"}'
# Response:
# {"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...","user_email":"user@example.com","user_nicename":"api-user","user_display_name":"API User"}
# 2. Verifica un token (opzionale, per validare lato server)
curl -s -X POST "https://tuosito.it/wp-json/jwt-auth/v1/token/validate" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
Usare il JWT per chiamate autenticate
// Esempio in JavaScript — client side
const token = localStorage.getItem('wp_jwt_token');
fetch('https://tuosito.it/wp-json/agencypilot/v1/site-stats', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => console.log(data));
Endpoint personalizzato con verifica JWT
Quando usi JWT, l’autenticazione è gestita dal plugin. Ma la permission_callback rimane tua responsabilità:
add_action('rest_api_init', function() {
register_rest_route('agencypilot/v1', '/managed-sites', [
'methods' => 'GET',
'permission_callback' => function($request) {
// JWT Auth imposta l'utente corrente se il token è valido
if (!is_user_logged_in()) {
return new WP_Error(
'rest_not_logged_in',
'Token non valido o scaduto',
['status' => 401]
);
}
// Verifica capability aggiuntive
if (!current_user_can('manage_options')) {
return new WP_Error(
'rest_forbidden',
'Permessi insufficienti',
['status' => 403]
);
}
return true;
},
'callback' => 'agencypilot_get_managed_sites',
]);
});
function agencypilot_get_managed_sites($request) {
$sites = get_option('agencypilot_managed_sites', []);
return rest_ensure_response($sites);
}
Refresh token: gestire la scadenza
I token JWT scadono. Il default del plugin è 7 giorni, ma per app mobile è meglio ridurre a 1 ora e implementare un refresh token. Ecco un pattern che usiamo in produzione:
// Estendi il plugin JWT con un endpoint di refresh
add_action('rest_api_init', function() {
register_rest_route('jwt-auth/v1', '/token/refresh', [
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => 'agencypilot_jwt_refresh',
]);
});
function agencypilot_jwt_refresh($request) {
$token = $request->get_header('authorization');
if (!$token || strpos($token, 'Bearer ') !== 0) {
return new WP_Error('missing_token', 'Token mancante', ['status' => 400]);
}
$token = substr($token, 7);
// Verifica il token anche se scaduto (entro 30 giorni)
$payload = agencypilot_decode_jwt_extended($token);
if (!$payload) {
return new WP_Error('invalid_token', 'Token non valido', ['status' => 401]);
}
// Genera un nuovo token
$user = get_user_by('login', $payload->data->user_login);
if (!$user) {
return new WP_Error('user_not_found', 'Utente non trovato', ['status' => 404]);
}
$new_token = agencypilot_generate_jwt($user);
return ['token' => $new_token];
}
Quando usare JWT
- SPA (React, Vue, Next.js) che usano WordPress come backend
- App mobile native (iOS, Android) che parlano con la REST API
- Sistemi dove non puoi memorizzare credenziali persistenti lato client
- Quando hai bisogno di scadenza automatica delle credenziali
Best practice di sicurezza JWT
- Memorizza il token in memoria o in cookie HttpOnly, non in localStorage (vulnerabile a XSS)
- Imposta una scadenza breve (1 ora per app, 24 ore per admin panel)
- Usa HTTPS ovunque. Un token intercettato è un account compromesso.
- Implementa una blacklist per revocare token prima della scadenza naturale
- Usa utenti con capability minime per i token API. Mai l’utente admin.
OAuth 2.0: Per Integrazioni Complesse con Terze Parti
OAuth 2.0 è il protocollo di autorizzazione usato da Google, GitHub, Facebook per le loro API. In WordPress, ha senso solo in scenari specifici: quando un servizio di terze parti deve accedere ai dati di WordPress per conto di un utente, senza che quell’utente condivida la sua password.
Per il 90% dei progetti di agenzia, OAuth 2.0 è overkill. Se stai costruendo un’integrazione tra tuoi servizi, usa Application Passwords. Se stai costruendo una SPA, usa JWT. Se stai creando un’app pubblica dove utenti esterni autorizzano l’accesso al loro WordPress, allora OAuth 2.0 è la risposta.
Setup OAuth 2.0 con WP OAuth Server
Il plugin più affidabile per OAuth 2.0 su WordPress è “WP OAuth Server” (versione gratuita disponibile su wordpress.org):
wp plugin install wp-oauth-server --activate
Dopo l’attivazione, configura un client OAuth dal menu del plugin:
- Vai in WP OAuth Server → Clients
- Clicca “Add New Client”
- Inserisci Client ID e Client Secret (generati automaticamente)
- Imposta il Redirect URI (l’URL dove l’utente viene rimandato dopo l’autorizzazione)
- Scegli i grant types: authorization_code per app web, client_credentials per server-to-server
Flusso Authorization Code (app web)
# Step 1: Redirect l'utente all'endpoint di autorizzazione
# Apri nel browser:
# https://tuosito.it/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://app.example.com/callback&scope=read
# Step 2: L'utente autorizza e viene rimandato al redirect_uri con un code
# https://app.example.com/callback?code=AUTH_CODE_HERE
# Step 3: Scambia il code con un access token
curl -s -X POST "https://tuosito.it/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=AUTH_CODE_HERE&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=https://app.example.com/callback"
# Response:
# {"access_token":"abc123...","expires_in":3600,"token_type":"Bearer","refresh_token":"def456..."}
Flusso Client Credentials (server-to-server)
curl -s -X POST "https://tuosito.it/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=read"
# Usa l'access token per le chiamate API
curl -s "https://tuosito.it/wp-json/wp/v2/posts" \
-H "Authorization: Bearer abc123..."
Sicurezza Endpoint: Oltre l’Autenticazione
L’autenticazione risponde alla domanda “chi sei?”. Ma non risponde a “cosa puoi fare?”. Un utente autenticato non dovrebbe poter fare tutto. Qui entra in gioco la permission_callback, che è l’ultimo strumento di difesa prima del tuo codice.
Pattern di autorizzazione per capability
// Endpoint solo per admin
register_rest_route('agencypilot/v1', '/settings', [
'methods' => 'POST',
'permission_callback' => function() {
return current_user_can('manage_options');
},
'callback' => 'agencypilot_update_settings',
]);
// Endpoint per utenti autenticati con ruolo specifico
register_rest_route('agencypilot/v1', '/reports', [
'methods' => 'GET',
'permission_callback' => function() {
return current_user_can('edit_posts');
},
'callback' => 'agencypilot_get_reports',
]);
// Endpoint pubblico con rate limiting custom
register_rest_route('agencypilot/v1', '/public-data', [
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => 'agencypilot_get_public_data',
]);
Rate limiting custom per endpoint pubblici
Gli endpoint pubblici (permission_callback => __return_true) sono vulnerabili ad abusi. WordPress non ha rate limiting nativo. Ecco come implementarlo:
add_action('rest_api_init', function() {
register_rest_route('agencypilot/v1', '/public-stats', [
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => 'agencypilot_public_stats_rate_limited',
]);
});
function agencypilot_public_stats_rate_limited($request) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$transient_key = 'rate_limit_' . md5($ip . 'public_stats');
$requests = get_transient($transient_key);
if ($requests === false) {
set_transient($transient_key, 1, MINUTE_IN_SECONDS);
} elseif ($requests >= 30) {
return new WP_Error(
'rate_limited',
'Troppe richieste. Riprova tra un minuto.',
['status' => 429]
);
} else {
set_transient($transient_key, $requests + 1, MINUTE_IN_SECONDS);
}
return [
'posts' => wp_count_posts()->publish,
'updated' => current_time('mysql'),
];
}
Per un rate limiting più serio (con Redis invece dei transient), dai un’occhiata alla nostra guida alle performance WordPress dove parliamo di configurazione Redis per cache.
Confronto Pratico: Quale Metodo Scegliere
Più che teoria, ecco la decisione basata su scenari reali che incontriamo come agenzia:
| Scenario | Metodo | Perché |
|---|---|---|
| Script bash che sincronizza post | Application Passwords | Semplice, nativo, nessun plugin |
| Dashboard React per clienti | JWT | Stateless, scadenza automatica, UX fluida |
| App mobile iOS/Android | JWT | Standard mobile, refresh token, nessuna sessione |
| Webhook da Stripe a WordPress | Application Passwords | Server-to-server, credenziali memorizzate su server |
| Marketplace dove utenti autorizzano terze parti | OAuth 2.0 | Delegazione di autorizzazione, scope limitati |
| Headless WordPress con Next.js | JWT + Cookie HttpOnly | SSR con token valido lato server |
| MCP Server che espone WordPress a AI | Application Passwords | Connessione persistente server-to-server, revocabile |
Errori Comuni che Rompono l’Autenticazione API
1. Basic Auth su HTTP
Basic Auth (usata dalle Application Passwords) invia le credenziali codificate in Base64 nell’header. Base64 non è crittografia: chiunque intercetti il traffico può decodificarlo in 2 secondi. Se il tuo sito non ha HTTPS, non usare Basic Auth. Punto.
Per forzare HTTPS su WordPress, aggiungi nel .htaccess (Apache) o nel blocco server Nginx:
# Nginx — redirect HTTP to HTTPS
server {
listen 80;
server_name tuosito.it;
return 301 https://$server_name$request_uri;
}
2. Credenziali nel codice frontend
Se metti Application Passwords o secret JWT in un file JavaScript pubblico, chiunque apra DevTools ha le tue credenziali. Questo errore è più comune di quanto pensi. Abbiamo trovato credenziali API in file .env serviti staticamente da build tool che non le hanno escluse.
Soluzione: usa un layer server-side (API route, funzione serverless, o endpoint PHP) che inietta le credenziali lato server e restituisce solo i dati al frontend.
3. permission_callback mancante o permissiva
Fino a WordPress 5.4, la permission_callback era opzionale. Molti tutorial vecchi la omettono o usano __return_true. Se注册i un endpoint senza permission_callback su WordPress 5.5+, ricevi un errore. Ma se usi __return_true su un endpoint sensibile, l’endpoint è pubblico. Verifica sempre.
4. Token JWT senza scadenza
Un JWT senza scadenza è un disastro di sicurezza. Se il token viene rubato, l’attaccante ha accesso perpetuo. Imposta sempre una scadenza nel plugin JWT:
// wp-config.php
define('JWT_AUTH_EXPIRATION', 3600); // 1 ora in secondi
5. Un solo utente API per tutto
Se usi un unico utente WordPress per tutte le integrazioni API e quell’utente viene compromesso, devi bloccare tutti i servizi che lo usano. Crea utenti separati per ogni integrazione: uno per il sync di AgencyPilot, uno per il webhook di Stripe, uno per la dashboard clienti. Se uno viene compromesso, revocarlo non impatta gli altri.
Endpoint di Test: Verificare la Configurazione
Per testare che l’autenticazione funzioni correttamente, ecco uno script di verifica che usiamo nei nostri ambienti staging prima di deployare in produzione:
#!/bin/bash
# test-api-auth.sh — verifica configurazione autenticazione REST API
WP_URL="https://staging.tuosito.it"
WP_USER="api-test-user"
WP_APP_PASS="xxxx xxxx xxxx xxxx xxxx xxxx"
echo "=== Test 1: Endpoint pubblico senza auth ==="
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${WP_URL}/wp-json/wp/v2/posts?per_page=1")
echo "Status: $HTTP_CODE (expected: 200)"
echo ""
echo "=== Test 2: Endpoint con Application Password ==="
RESPONSE=$(curl -s -w "\n%{http_code}" "${WP_URL}/wp-json/wp/v2/users/me" \
-u "${WP_USER}:${WP_APP_PASS// /}")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | head -n -1)
echo "Status: $HTTP_CODE (expected: 200)"
echo "User: $(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin).get('name','ERROR'))" 2>/dev/null)"
echo ""
echo "=== Test 3: Auth fallita con password errata ==="
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${WP_URL}/wp-json/wp/v2/users/me" \
-u "${WP_USER}:wrong-password")
echo "Status: $HTTP_CODE (expected: 401)"
echo ""
echo "=== Test 4: Endpoint protetto senza auth ==="
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${WP_URL}/wp-json/agencypilot/v1/site-stats")
echo "Status: $HTTP_CODE (expected: 401)"
echo ""
echo "=== Test 5: Endpoint protetto con auth corretta ==="
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${WP_URL}/wp-json/agencypilot/v1/site-stats" \
-u "${WP_USER}:${WP_APP_PASS// /}")
echo "Status: $HTTP_CODE (expected: 200)"
echo ""
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ Tutti i test passati"
else
echo "❌ Alcuni test falliti — verifica configurazione"
fi
Salva questo script in scripts/test-api-auth.sh ed eseguilo dopo ogni cambio di configurazione API. Noi lo lanciamo automaticamente nel pipeline CI prima di ogni deploy.
FAQ: Autenticazione REST API WordPress
Application Passwords sono sicure?
Sì, se usi HTTPS. Le Application Passwords usano Basic Auth, che invia le credenziali codificate in Base64. Base64 è reversibile, quindi senza HTTPS le credenziali sono visibili a chiunque intercetti il traffico. Con HTTPS (obbligatorio nel 2026), il traffico è crittografato e le Application Passwords sono sicure per uso server-side.
Posso usare l’API REST di WordPress senza autenticazione?
Dipende dall’endpoint. Gli endpoint pubblici (lettura di post pubblicati, pagine, termini) sono accessibili senza autenticazione. Qualsiasi operazione di scrittura (creare, modificare, eliminare) richiede autenticazione. Gli endpoint personalizzati possono essere pubblici o protetti, a seconda della permission_callback che definisci.
JWT o Application Passwords per un’SPA?
JWT. Le Application Passwords non sono adatte per il frontend perché le credenziali sarebbero visibili nel codice JavaScript. JWT permette al client di autenticarsi una volta con username/password, ricevere un token temporaneo, e usare solo quello per le chiamate successive.
Come revoco un token JWT prima della scadenza?
WordPress non ha un meccanismo nativo di revoca JWT. Devi implementarlo: salva gli ID dei token revocati in una tabella custom o nei transient, e verifica nel permission_callback che il token non sia in blacklist. In alternativa, cambia la JWT_AUTH_SECRET_KEY in wp-config.php: tutti i token esistenti diventano istantaneamente non validi.
OAuth 2.0 è necessario per la mia agenzia?
Probabilmente no. OAuth 2.0 ha senso quando un servizio esterno deve accedere a WordPress per conto di un utente, senza che l’utente condivida la password. Se sei tu a controllare sia il client che il server (es. la tua dashboard che parla con il tuo WordPress), Application Passwords o JWT sono più semplici e altrettanto sicuri.
Quali capability servono per un utente API?
Dipende da cosa fa l’API. Per sola lettura: read. Per gestire contenuti: edit_posts. Per gestire plugin e temi: manage_options o activate_plugins. Crea un ruolo personalizzato con solo le capability necessarie usando add_role() nel functions.php.