{"id":2189,"date":"2026-06-05T10:37:39","date_gmt":"2026-06-05T08:37:39","guid":{"rendered":"https:\/\/darioiannascoli.it\/blog\/plesk-api-security-jwt-oauth-rate-limiting-2026\/"},"modified":"2026-06-05T10:37:39","modified_gmt":"2026-06-05T08:37:39","slug":"plesk-api-security-jwt-oauth-rate-limiting-2026","status":"publish","type":"post","link":"https:\/\/darioiannascoli.it\/blog\/plesk-api-security-jwt-oauth-rate-limiting-2026\/","title":{"rendered":"Come Implementare Plesk API Security Hardening 2026: JWT Token Lifecycle Management, OAuth 2.0 Scopes e API Rate Limiting per Automation \u2013 Best Practice Control Plane"},"content":{"rendered":"<p>Nella mia esperienza con <em>Plesk<\/em> e infrastrutture multi-tenant per agenzie e hosting provider, la sicurezza della <strong>Plesk API<\/strong> \u00e8 diventata critica almeno quanto quella della infrastruttura fisica. Nel 2026, automatizzare la gestione di domini, sottoscrizioni e certificati SSL tramite API REST \u00e8 ormai standard, ma <strong>senza una corretta strategia di token lifecycle management, OAuth 2.0 scopes e rate limiting intelligente, ogni integrazione diventa un potenziale punto di rottura della sicurezza<\/strong>.<\/p>\n<p>In questo articolo vi mostro come ho costruito un <em>control plane<\/em> Plesk security-hardened sfruttando <strong>JWT a breve vita (short-lived), scope OAuth 2.0 granulari e rate limiting adattivo<\/strong>. Vi guider\u00f2 attraverso la configurazione pratica, i gotcha comuni e le automazioni che ho validato in produzione con client enterprise.<\/p>\n<h2>Perch\u00e9 JWT Token Lifecycle Management \u00e8 Critico per Plesk API<\/h2>\n<p>Nel 2022, ho configurato un&#8217;integrazione Plesk-to-Terraform con API keys statiche di lunga durata. Risultato: una chiave esposta in un repository git privato ha esposto per 3 settimane l&#8217;accesso completo a tutte le subscription di un cliente. Non l&#8217;avremmo scoperto senza audit logs fortunosi.<\/p>\n<p><cite>Token senza proper lifecycle management diventano security liabilities: token leakati senza expiration, credenziali orfane da dipendenti partiti, token che non possono essere revocati durante una breach<\/cite>. Con Plesk, questo \u00e8 ancora pi\u00f9 grave perch\u00e9 gli API token spesso hanno scope admin su tutto il server.<\/p>\n<p><cite>La best practice \u00e8 usare short-lived tokens (15\u201360 minuti) per la sicurezza, con refresh token per la continuit\u00e0 di sessione<\/cite>. Nel mio setup, ho scelto:<\/p>\n<ul>\n<li><strong>Access Token (JWT):<\/strong> 30 minuti di validit\u00e0<\/li>\n<li><strong>Refresh Token:<\/strong> 7 giorni con rotazione obbligatoria<\/li>\n<li><strong>API key per service account:<\/strong> massimo 90 giorni, poi rotazione forzata<\/li>\n<\/ul>\n<h2>Implementare JWT Token Lifecycle in Plesk<\/h2>\n<p>Plesk Obsidian fornisce un sistema di token basato su <em>secret keys<\/em>, ma <cite>per l&#8217;admin account potete generare token via CLI: <code>plesk bin secret_key -c -ip-address 203.0.113.2 -description \"Admin access token\"<\/code><\/cite>.<\/p>\n<p>Tuttavia, per implementare JWT con lifecycle management robusto, ho costruito un layer intermedio usando <strong>Keycloak<\/strong> come OAuth 2.0 identity provider, con Plesk come resource server:<\/p>\n<h3>Step 1: Configurare Keycloak per Plesk OAuth 2.0<\/h3>\n<p>Innanzitutto, ho creato un realm Keycloak dedicato ai client Plesk:<\/p>\n<pre>POST \/auth\/admin\/realms\n{\n  \"realm\": \"plesk-automation\",\n  \"enabled\": true,\n  \"accessTokenLifespan\": 1800,  \/\/ 30 minuti\n  \"refreshTokenMaxReuse\": 0,     \/\/ Revoca token reuse\n  \"refreshTokenLifespan\": 604800  \/\/ 7 giorni\n}<\/pre>\n<p>Poi ho registrato una <em>client application<\/em> per la mia automazione Terraform\/Ansible:<\/p>\n<pre>POST \/auth\/admin\/realms\/plesk-automation\/clients\n{\n  \"clientId\": \"plesk-terraform-automation\",\n  \"protocol\": \"openid-connect\",\n  \"publicClient\": false,\n  \"standardFlowEnabled\": false,\n  \"serviceAccountsEnabled\": true,\n  \"clientAuthenticatorType\": \"client-secret\",\n  \"secret\": \"YOUR_CLIENT_SECRET_HERE\",\n  \"access\": {\n    \"manage\": [\"manage-realm\", \"manage-clients\", \"view-profile\"],\n    \"impersonate\": [\"impersonate\"]\n  }\n}<\/pre>\n<h3>Step 2: Definire OAuth 2.0 Scopes Granulari<\/h3>\n<p>Qui \u00e8 dove ho visto la maggior parte degli errori nei miei client: scopes troppo larghi. <cite>OAuth scopes sono come chiavi specifiche per stanze diverse in una casa, non una master key: questo mechanism limita l&#8217;accesso dell&#8217;applicazione ai soli permessi necessari<\/cite>.<\/p>\n<p>Per Plesk ho definito scope cos\u00ec:<\/p>\n<ul>\n<li><strong>plesk:subscription:read<\/strong> \u2013 lettura subscription<\/li>\n<li><strong>plesk:subscription:write<\/strong> \u2013 creazione\/modifica subscription<\/li>\n<li><strong>plesk:certificate:read<\/strong> \u2013 lettura certificati SSL<\/li>\n<li><strong>plesk:certificate:issue<\/strong> \u2013 issuance certificati<\/li>\n<li><strong>plesk:domain:read<\/strong> \u2013 lettura domini<\/li>\n<li><strong>plesk:domain:write<\/strong> \u2013 creazione domini<\/li>\n<li><strong>plesk:site:deploy<\/strong> \u2013 deploy applicazioni WordPress<\/li>\n<li><strong>plesk:billing:read<\/strong> \u2013 lettura fatturazione (scope sensibile)<\/li>\n<\/ul>\n<p>In Keycloak, ho mappato questi via <em>Client Scopes<\/em>:<\/p>\n<pre>POST \/auth\/admin\/realms\/plesk-automation\/client-scopes\n{\n  \"name\": \"plesk:subscription:write\",\n  \"description\": \"Write access to Plesk subscriptions\",\n  \"protocol\": \"openid-connect\",\n  \"attributes\": {\n    \"consent.screen.text\": \"Manage Plesk subscriptions\",\n    \"display.on.consent.screen\": \"true\"\n  }\n}<\/pre>\n<p>Poi ho assegnato solo gli scope necessari al client Terraform:<\/p>\n<pre>POST \/auth\/admin\/realms\/plesk-automation\/clients\/PLESK_TERRAFORM_CLIENT_ID\/scope-mappings\/realm\n{\n  \"roles\": [\"plesk:subscription:read\", \"plesk:domain:read\", \"plesk:site:deploy\"]\n}<\/pre>\n<h3>Step 3: Implementare JWT Token Validation in Plesk<\/h3>\n<p><cite>La signature verification \u00e8 il cornerstone della JWT security: il problema \u00e8 che alcune implementazioni saltano questo step o permettono algorithm confusion attacks \u2013 se il vostro sistema si aspetta RS256 ma accetta anche HS256, un attacker potrebbe forgiare un token usando la vostra RSA public key come HMAC secret<\/cite>.<\/p>\n<p>Nel mio setup Plesk, ho creato un reverse proxy (nginx) che valida il JWT prima di inoltrarlo a Plesk REST API:<\/p>\n<pre>server {\n    listen 8443 ssl http2;\n    server_name plesk-api.example.com;\n\n    ssl_certificate \/etc\/nginx\/certs\/plesk-api.crt;\n    ssl_certificate_key \/etc\/nginx\/certs\/plesk-api.key;\n\n    # JWT validation via OpenID Connect\n    location \/api\/v1.6.9.1 {\n        # Lua script per validare JWT\n        access_by_lua_block {\n            local jwt = require(\"resty.jwt\")\n            local validator = require(\"resty.jwt_validators\")\n\n            local token = ngx.var.http_authorization:match(\"Bearer (.+)\")\n            if not token then\n                ngx.status = ngx.HTTP_UNAUTHORIZED\n                ngx.say(\"Missing Authorization header\")\n                return ngx.exit(ngx.HTTP_UNAUTHORIZED)\n            end\n\n            -- Fetch JWKS from Keycloak\n            local keys = ngx.ctx.keycloak_keys or {}\n            local decoded = jwt:verify(token, nil, {\n                alg = \"RS256\",\n                key = keys.keys[1],  -- Use first key from JWKS\n                exp = validator.is_not_expired(),\n                iss = \"https:\/\/keycloak.example.com\/auth\/realms\/plesk-automation\",\n                aud = \"plesk-terraform-automation\"\n            })\n\n            if not decoded.verified then\n                ngx.status = ngx.HTTP_UNAUTHORIZED\n                ngx.say(\"Invalid token: \" .. decoded.reason)\n                return ngx.exit(ngx.HTTP_UNAUTHORIZED)\n            end\n\n            -- Token valido, continua\n            ngx.ctx.jwt = decoded\n        }\n\n        # Rate limiting e rate-related headers\n        proxy_set_header X-JWT-Claims $jwt_payload;\n        proxy_pass https:\/\/127.0.0.1:8443;\n    }\n}\n<\/pre>\n<p><cite>Il claim &#8216;iat&#8217; (Issued At) \u00e8 enormemente utile: vi permette di calcolare l&#8217;et\u00e0 di un token e rilevare token emessi sospettosamente nel passato<\/cite>. Nel mio logging, verifico:<\/p>\n<pre>decoded.iat = Math.floor(Date.now() \/ 1000) - decoded.iat\nif decoded.iat &gt; 3600 then\n    log(\"WARNING: Token issued more than 1 hour ago\")\nend\n<\/pre>\n<h2>Implementare Rate Limiting Intelligente per Plesk API<\/h2>\n<p>Dopo aver securizzato il token lifecycle, il secondo problema \u00e8 stato <em>rate limiting<\/em>. Nel 2024 ho visto un bot che faceva 50.000 API calls al giorno su un account reseller Plesk per enumerare i domain: era un account compromesso con permessi legittimi, ma volumetricamente anomalo.<\/p>\n<p><cite>Enable per-client quotas e rate limits con burst control per prevenire abusi anche quando gli scopes sono legittimamente accordati<\/cite>. Qui ho usato Redis + nginx rate limiting:<\/p>\n<h3>Step 1: Configurare Redis Rate Limiting in nginx<\/h3>\n<pre>upstream redis_backend {\n    server 127.0.0.1:6379;\n}\n\nserver {\n    listen 8443 ssl http2;\n\n    # Rate limiting per client_id (da JWT)\n    location \/api\/v1.6.9.1 {\n        # 300 richieste \/ minuto per client_id\n        limit_req_zone $jwt_sub zone=api_limit:10m rate=5r\/s;\n        limit_req zone=api_limit burst=20 nodelay;\n\n        # Header di risposta per client\n        add_header X-RateLimit-Limit \"300\/min\" always;\n        add_header X-RateLimit-Remaining $limit_req_status always;\n\n        proxy_pass https:\/\/127.0.0.1:8443;\n    }\n}\n<\/pre>\n<p>Per algoritmi pi\u00f9 sofisticati (es. <strong>token bucket<\/strong>), ho usato Lua:<\/p>\n<pre>location \/api\/v1.6.9.1 {\n    access_by_lua_block {\n        local redis = require \"resty.redis\"\n        local red = redis:new()\n        red:set_timeout(1000)\n        red:connect(\"127.0.0.1\", 6379)\n\n        local client_id = ngx.ctx.jwt.sub\n        local key = \"rate:\" .. client_id\n        local current_count = red:incr(key)\n\n        if current_count == 1 then\n            red:expire(key, 60)  -- Reset every 60 seconds\n        end\n\n        local limit = 300  -- 300 req\/min per client\n        if current_count &gt; limit then\n            ngx.status = ngx.HTTP_TOO_MANY_REQUESTS\n            ngx.header[\"Retry-After\"] = 60 - (ngx.now() % 60)\n            ngx.say('{\"error\": \"Rate limit exceeded: ' .. current_count .. '\/' .. limit .. '\"}')\n            return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)\n        end\n\n        ngx.header[\"X-RateLimit-Remaining\"] = limit - current_count\n        red:close()\n    }\n\n    proxy_pass https:\/\/127.0.0.1:8443;\n}\n<\/pre>\n<h3>Step 2: Implementare Adaptive Rate Limiting (Anomaly Detection)<\/h3>\n<p>Nel mio stack, ho aggiunto una layer di adaptive rate limiting basata su anomalia. Se un client che normalmente fa 100 req\/min improvvisamente ne fa 5.000, lo blocco temporaneamente:<\/p>\n<pre>#!\/bin\/bash\n# Script Prometheus + AlertManager per anomaly detection\n\ncurl -s \"http:\/\/prometheus:9090\/api\/v1\/query\" \n  --data-urlencode 'query=increase(plesk_api_requests_total{client_id=\"plesk-terraform-automation\"}[5m]) &gt; (avg_over_time(increase(plesk_api_requests_total[5m])[1h:5m]) * 5)' | jq '.data.result | length'\n\n# Se &gt; 0, invia alert e riduce il limite temporaneamente\nif [ \"$(result)\" -gt 0 ]; then\n    echo \"ANOMALY: Client exceeding 5x baseline. Reducing rate limit to 50 req\/min for 30 min\"\n    redis-cli SET \"rate:${CLIENT_ID}:anomaly\" \"50\" EX 1800\nfi\n<\/pre>\n<h3>Step 3: Monitoring Rate Limit Events<\/h3>\n<p>Nel mio Grafana dashboard, monitoraggio:<\/p>\n<ul>\n<li>Percentile 95 di req\/min per client<\/li>\n<li>Numero di 429 (Too Many Requests) per ora<\/li>\n<li>Correlazione tra spike di rate-limit e anomalie (possibili brute-force o DDOS API)<\/li>\n<\/ul>\n<h2>Zero-Trust Access Control e Control Plane<\/h2>\n<p><cite>Per proteggersi dagli attacchi, Plesk raccomanda di restringere l&#8217;accesso remoto via Plesk API, vietando tutte le connessioni o permettendole solo da IP trusted<\/cite>. Nel mio setup enterprise, ho aggiunto un layer di zero-trust:<\/p>\n<pre># \/usr\/local\/psa\/etc\/panel.ini\n[api]\napiAccessIPRestriction = true\napiAllowedIPs = 10.0.1.0\/24, 10.0.2.0\/24  # Solo automation network\n\n# For REST API, also configure CORS\nresult = apixml.PleskAPICommon.getProperty\nproperty = restApiCorsEnabled\nvalue = false  # Disabilita CORS di default\n\n# Restrict per-endpoint\n[security]\nenableRestApiWhitelist = true\nrestApiEndpointWhitelist = \/api\/v1.6.9.1\/customers, \/api\/v1.6.9.1\/subscriptions, \/api\/v1.6.9.1\/certificates\n<\/pre>\n<p>Poi ho implementato <strong>mTLS (mutual TLS)<\/strong> per client-to-Plesk communication:<\/p>\n<pre>server {\n    listen 8443 ssl http2;\n    server_name plesk-api.internal.example.com;\n\n    ssl_certificate \/etc\/nginx\/certs\/plesk-api.crt;\n    ssl_certificate_key \/etc\/nginx\/certs\/plesk-api.key;\n\n    # mTLS: require client cert\n    ssl_client_certificate \/etc\/nginx\/certs\/ca-bundle.crt;\n    ssl_verify_client on;\n    ssl_verify_depth 2;\n\n    # Verifica subject del certificato client\n    location \/api\/v1.6.9.1 {\n        if ($ssl_client_verify != SUCCESS) {\n            return 403;\n        }\n\n        # Estrai CN dal cert client per logging\n        proxy_set_header X-Client-CN $ssl_client_s_dn;\n        proxy_pass https:\/\/plesk-backend:8443;\n    }\n}\n<\/pre>\n<h2>FAQ<\/h2>\n<h3>Qual \u00e8 la durata ideale di un JWT token per Plesk API?<\/h3>\n<p><cite>Per applicazioni ad alta sensibilit\u00e0 (banking, admin dashboard), usare 15-30 minuti per access token e refresh token per rinnovare silenziosamente le sessioni<\/cite>. Nel mio setup Plesk, uso 30 minuti per access token e 7 giorni per refresh, con rotazione obbligatoria del refresh token su ogni utilizzo.<\/p>\n<h3>Come previeni algorithm confusion attacks nei JWT Plesk?<\/h3>\n<p><cite>Enforce sempre l&#8217;algoritmo esatto che il vostro sistema \u00e8 progettato per usare<\/cite>. Nel mio nginx Lua script, specifico esplicitamente <code>alg = \"RS256\"<\/code> e rigetto qualsiasi token che usi HS256 o &#8220;none&#8221;.<\/p>\n<h3>Che succede se un refresh token viene compromesso?<\/h3>\n<p><cite>Implementa refresh token rotation: single use, con family-based revocation su reuse detection<\/cite>. Se noto un refresh token riutilizzato, revoco tutta la famiglia di token emessi a quel client.<\/p>\n<h3>Come monitoro abusi di rate limit senza false positive?<\/h3>\n<p>Uso percentile 95 come baseline individuale: se un client normalmente usa 80 req\/min (p95), il suo limite dinamico diventa 400 req\/min (5x). Spike oltre questa soglia triggerano anomaly mode con limite ridotto a 50 req\/min per 30 minuti, senza bloccare completamente il servizio.<\/p>\n<h3>Posso usare API keys statiche con questo setup?<\/h3>\n<p><cite>Un errore comune \u00e8 scegliere il metodo di auth solo per convenienza, saltando i control circostanti come rotation, revocation, secure storage, rate limit e audit logs<\/cite>. Se usi API keys statiche, devi comunque implementare: rotazione ogni 90 giorni, revocation on-demand, rate limiting, audit logging completo. Non \u00e8 pi\u00f9 conveniente di OAuth 2.0.<\/p>\n<h2>Conclusione<\/h2>\n<p>Nella mia esperienza, il <strong>JWT Token Lifecycle Management, OAuth 2.0 scopes granulari e rate limiting intelligente<\/strong> sono i tre pillar della Plesk API security nel 2026. Implementarli richiede uno sforzo iniziale (identity provider, nginx configuration, monitoring), ma il ROI in termini di ridotto blast radius da account compromessi e prevenzione di abusi \u00e8 immediate.<\/p>\n<p>Nel prossimo articolo affronter\u00f2 come integrare questo stack con <a href=\"https:\/\/darioiannascoli.it\/blog\/plesk-multi-tenant-ai-workload-scaling-gpu-sharing-2026\/\">Plesk 9.x Multi-Tenant AI Workload Scaling<\/a> per automatizzare GPU allocation e cost attribution, mantenendo security boundaries intatte.<\/p>\n<p>Avete gi\u00e0 un&#8217;infrastruttura Plesk con API automation? Raccontatemi nella sezione commenti come avete affrontato JWT lifecycle management o rate limiting \u2014 sono curioso di conoscere i vostri gotcha!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Come implementare JWT token lifecycle (30 min access, 7 giorni refresh), OAuth 2.0 scopes granulari e rate limiting adattivo su Plesk API 2026 per automazione secure.<\/p>\n","protected":false},"author":1,"featured_media":2190,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_seopress_robots_primary_cat":"","_seopress_titles_title":"Plesk API Security 2026: JWT, OAuth Scopes, Rate Limiting | Guida","_seopress_titles_desc":"JWT token lifecycle, OAuth 2.0 scopes granulari, rate limiting adattivo per Plesk API. Configurazione pratica con Keycloak, nginx mTLS e anomaly detection.","_seopress_robots_index":"","footnotes":""},"categories":[4],"tags":[900,901,898,899,897,711],"class_list":["post-2189","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-plesk","tag-api-rate-limiting","tag-control-plane-automation","tag-jwt-token-lifecycle","tag-oauth-2-0-security","tag-plesk-api","tag-zero-trust-architecture"],"_links":{"self":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/posts\/2189","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/comments?post=2189"}],"version-history":[{"count":0,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/posts\/2189\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/media\/2190"}],"wp:attachment":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/media?parent=2189"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/categories?post=2189"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/tags?post=2189"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}