Torna al blog Operazioni SMS

Concorrenza nell’invio di OTP via SMS: stati, blocchi e reinvii

Una guida pratica per coordinare richieste di OTP concorrenti, ridurre i duplicati e gestire con prudenza i timeout dall’esito incerto.

Diagramma di richieste di OTP via SMS coordinate tramite stati e un blocco temporaneo

Il problema: più richieste per una sola verifica

Un tocco ripetuto, due schede aperte o più richieste che arrivano quasi contemporaneamente possono avviare più di un flusso OTP. Può anche succedere che due processi elaborino nello stesso momento richieste equivalenti. Il risultato può essere la generazione di codici diversi, la creazione di più tentativi di invio o la consegna all’utente di un codice che non corrisponde più allo stato dell’applicazione.

La limitazione delle richieste e il blocco temporaneo possono aiutare a controllarne la frequenza, ma da soli non garantiscono che due processi concorrenti non richiedano la stessa operazione. Il coordinamento deve essere gestito anche nello storage o nel meccanismo condiviso utilizzato dall’applicazione.

  • Verifica se le richieste concorrenti appartengono alla stessa verifica, non solo se provengono dallo stesso numero.
  • Separa la protezione contro le richieste eccessive dalla prevenzione dell’elaborazione duplicata.
  • Non interpretare l’assenza di una risposta immediata come prova che l’invio non sia stato avviato.
Il problema: più richieste per una sola verifica

Separa la verifica, il codice e il tentativo di invio

Modella separatamente tre elementi: la richiesta di autenticazione, l’OTP associato a quella richiesta e ogni singolo tentativo di invio del messaggio. Questa separazione facilita la distinzione tra una nuova verifica e un reinvio e consente di conservare la cronologia anche quando cambia lo stato del codice.

Prima di definire le regole, stabilisci cosa significa «stessa operazione» nel tuo prodotto. Una richiesta ripetuta per una verifica attiva può richiedere un trattamento diverso da una nuova verifica avviata dopo il completamento, l’annullamento o la scadenza della precedente.

  • Associa ogni verifica a un identificativo interno stabile.
  • Registra ogni tentativo di invio con un proprio identificativo e un riferimento alla verifica.
  • Non usare il numero di telefono come unico criterio di deduplicazione: lo stesso utente può avviare verifiche legittime per finalità o sessioni diverse.
Separa la verifica, il codice e il tentativo di invio

Definisci una chiave operativa con un ambito chiaro

Una chiave operativa consente alle richieste equivalenti di convergere sullo stesso record di controllo. Può essere composta da un identificativo dell’utente o della sessione, dalla finalità dell’autenticazione e da una finestra temporale definita dalla policy del servizio. La scelta concreta dipende da come l’applicazione delimita le sessioni e le verifiche: non esiste una combinazione valida per tutti.

La finestra temporale dovrebbe servire a raggruppare le ripetizioni di un’operazione, non a bloccare indefinitamente nuove verifiche. Mantieni separate la chiave di deduplicazione e le regole di frequenza o di blocco temporaneo: hanno obiettivi correlati, ma distinti.

  • Definisci quali elementi rendono equivalenti due richieste.
  • Usa una finestra esplicita e documenta quando si considera avviata una nuova operazione.
  • Evita di includere dati sensibili non necessari in una chiave visibile o nei registri operativi.

Usa stati espliciti e un’acquisizione atomica

Rappresenta il ciclo con stati comprensibili, per esempio: richiesta, in elaborazione, invio accettato dal provider, esito incerto, completata, non riuscita o scaduta. Adatta nomi e transizioni alle informazioni realmente restituite dal tuo sistema e dal provider; non contrassegnare un invio come accettato solo perché è stato creato un’attività interna.

La transizione che acquisisce un’operazione deve essere atomica. In pratica, un worker prova a modificare il record da uno stato acquisibile a «in elaborazione» e prosegue solo se la transizione è riuscita. Se un altro worker ha già acquisito l’operazione, il secondo dovrebbe consultare lo stato esistente e applicare la policy definita, non avviare un altro invio per impostazione predefinita.

L’atomicità dipende dal meccanismo di coordinamento utilizzato. Un blocco in memoria di un singolo processo, da solo, non coordina più istanze dell’applicazione. Verifica che il controllo scelto funzioni su tutti i worker che potrebbero ricevere la stessa operazione.

  • Definisci gli stati e le transizioni consentite prima di implementare i tentativi.
  • Fai in modo che l’acquisizione e la registrazione dello stato avvengano come un’unica operazione indivisibile nel meccanismo condiviso.
  • Decidi quale risposta riceve una richiesta ripetuta quando l’operazione è già in corso: attendere, consultare lo stato o restituire una risposta «in corso».
  • Prevedi una modalità controllata per recuperare le operazioni abbandonate, senza trasformare automaticamente ogni operazione datata in un nuovo invio.

Definisci quando riutilizzare il codice e quando avviarne un altro

Una richiesta duplicata mentre una verifica è ancora valida può essere trattata come una consultazione o una ripetizione della stessa operazione. Se la policy di sicurezza e l’esperienza utente lo consentono, l’applicazione può mantenere valido l’OTP esistente ed evitare di generare un altro codice. Questa decisione dovrebbe essere coordinata con la scadenza e con le regole di invalidazione utilizzate dal sistema.

Generare un nuovo codice può essere legittimo quando inizia una nuova verifica, quando il codice precedente non è più valido o quando una policy documentata ne richiede la sostituzione. In tal caso, stabilisci cosa succede ai codici precedenti e assicurati che la convalida applichi la regola in modo coerente. Non esiste una policy unica adatta a tutte le applicazioni.

  • Documenta quale evento costituisce una nuova verifica e quale rappresenta solo un nuovo tentativo della richiesta.
  • Stabilisci come gestire un OTP ancora valido quando arriva una richiesta ripetuta.
  • Se viene emesso un nuovo codice, definisci esplicitamente se quello precedente resta valido o viene invalidato.
  • Allinea la risposta dell’interfaccia allo stato effettivo: non indicare che è stato inviato un nuovo codice se l’applicazione non ha avviato un nuovo tentativo.

Considera i timeout come un esito incerto

Se la connessione si interrompe o scade il tempo di attesa dopo l’avvio di una richiesta al provider, potrebbe non essere possibile determinare se il provider l’abbia ricevuta o accettata. Il timeout descrive ciò che ha osservato la tua applicazione; da solo non dimostra che l’SMS non sia stato inviato.

Registra il tentativo come incerto quando non puoi confermarne l’esito. Prima di riprovare, verifica se l’API o la connessione di invio offre una chiave di idempotenza, una consultazione dello stato o un altro meccanismo documentato per riconoscere la stessa richiesta. Se non esiste un modo verificabile per risolvere l’incertezza, applica una policy esplicita di gestione del rischio e comunica l’incertezza al sistema richiedente; evita i tentativi automatici e alla cieca.

  • Conserva l’identificativo interno del tentativo e l’eventuale riferimento restituito dal provider.
  • Distingui «nessuna risposta ricevuta» da «richiesta rifiutata dal provider».
  • Se viene autorizzato un nuovo tentativo, registralo come tale e mantieni il collegamento con il tentativo incerto.
  • Non promettere che l’utente riceverà un solo SMS quando non è possibile confermare l’esito del primo tentativo.

La deduplicazione interna e l’idempotenza del provider non sono la stessa cosa

La deduplicazione interna evita che la tua piattaforma elabori più volte un’operazione che considera equivalente. L’idempotenza di un’API di invio, quando è disponibile e documentata, consente al provider di riconoscere le richieste ripetute secondo le regole di quell’API. L’una non implica l’altra.

Prima di affidarti a una garanzia esterna, consulta la documentazione tecnica dell’API o della connessione utilizzata. Verifica l’ambito della chiave, per quanto tempo viene conservata, quale risposta viene restituita in caso di ripetizione e se copre davvero il caso di timeout che devi risolvere. Non presumere che HTTP o SMPP, da soli, offrano una deduplicazione applicativa per un OTP.

  • Mantieni un controllo interno anche se il provider offre l’idempotenza.
  • Non riutilizzare alla cieca la stessa chiave senza conoscerne l’ambito e il comportamento documentati.
  • Se non esiste una garanzia documentata, considera i reinvii come potenziali duplicati.

Mantieni la tracciabilità e distingui i diversi esiti

Registra l’identificativo dell’operazione, quello di ogni tentativo, le transizioni di stato, il motivo di un reinvio e l’esito comunicato dal provider. Limita i dati personali registrati e applica controlli di accesso e periodi di conservazione coerenti con i tuoi obblighi. Questi registri consentono di ricostruire le decisioni prese da ciascun componente quando sono arrivate richieste concorrenti.

Uno stato comunicato dal provider va descritto come tale. Non trasformarlo in un’affermazione di ricezione sul telefono se non ci sono prove che lo dimostrino. I registri di invio servono per la gestione operativa e la diagnosi: non garantiscono che l’utente abbia ricevuto l’SMS o completato la verifica.

  • Separa l’esito della richiesta alla tua API, quello comunicato dal provider e l’esito della verifica dell’OTP.
  • Annota il motivo del reinvio e chi, o quale componente, lo ha autorizzato.
  • Usa identificativi correlabili nei registri interni senza esporre il codice OTP nei dati di telemetria.
  • Testa richieste ripetute e parallele, errori tra le transizioni e timeout con esiti non confermati.
FAQ

Domande frequenti

Una chiave operativa garantisce che venga inviato un solo SMS?

No, non da sola. Può aiutare a impedire che la tua applicazione elabori più volte un’operazione equivalente, se l’acquisizione è coordinata in modo atomico. Non garantisce che il provider abbia ricevuto una sola richiesta né che il messaggio arrivi una sola volta al dispositivo.

Devo generare un nuovo OTP ogni volta che l’utente tocca «rinvia»?

Non esiste una regola universale. Definisci se l’azione ripete una verifica ancora valida o ne avvia una nuova e specifica come viene gestito il codice precedente. La policy deve essere coerente tra generazione, scadenza, invalidazione e convalida.

Cosa fare quando l’invio termina con un timeout?

Consideralo incerto se non puoi sapere se il provider ha ricevuto la richiesta. Consulta i meccanismi documentati disponibili e non presumere che il messaggio non sia stato accettato. Se è consentito un altro tentativo, registra la decisione e il collegamento con il tentativo precedente.

Uno stato di consegna conferma che l’SMS è arrivato al telefono?

Non equiparare le due cose senza prove specifiche. Registra lo stato come esito comunicato dal provider e distingui questo dato dalla ricezione verificata sul dispositivo e dal completamento dell’autenticazione.

Fonti consultate

  1. NIST Digital Identity GuidelinesNIST
  2. OWASP Authentication Cheat SheetOWASP
  3. ClickSend: Envíos de OTP por SMSClickSend