Prima di tutto, lo so, non reinventare la ruota ... Ma c'è una buona ragione per questo.
Quindi ho implementato un generico algoritmo di generazione / convalida del codice di autenticazione a 2 fattori per riutilizzarlo in più applicazioni e renderlo stateless (l'algoritmo non ha bisogno di memorizzare nulla nel database, e non non richiede alcun dato utente.)
L'applicazione che utilizza il codice è responsabile di impostare un carico utile e convalidarlo all'interno del processo generico 2 fa.
Quindi ecco come funzionerebbe ...
1 - L'applicazione richiede un nuovo codice 2FA che trasmette un carico utile:
1.1 - payload: dati arbitrari, come ID utente e ID applicazione.
1.2 - Viene creato un codice casuale a 7 cifre (utilizzando librerie casuali crittografiche sicure)
1.3: viene selezionato un tempo di scadenza, ovvero 3 minuti dall'ora corrente, espresso in epoch tempo in secondi.
1.4 - dati: (scadenza, payload, codice)
1.5 - i dati vengono crittografati utilizzando CTR AES con un IV generato a caso di 128 bit e una chiave segreta
1.6 - Sia i dati crittografati che IV vengono digeriti tramite un HMAC che utilizza un'altra chiave segreta, al fine di firmare questi due valori.
1.7 - token finale: "dati crittografati base64: IV: firma"
1.8 - il token viene restituito all'interfaccia utente utente (come un'app mobile o un sito web di accesso)
1.9 - il codice viene inviato tramite SMS o Email
2 - L'applicazione invia il token originale e il codice fornito dall'utente al back-end
2.1 - il token è decodificato e la firma è validata
2.2 - Il token è decrittografato, la verifica della scadenza e il codice utente viene confrontato con il codice token
2.3 - Il carico utile viene restituito all'applicazione, che esegue qualsiasi convalida aggiuntiva come il recupero di un utente dall'ID utente nel payload, ecc ...
È rotto? Potresti chiedere perché non memorizzare solo il codice di 7 cifre nel database, ma l'idea è di renderlo completamente stateless e generico.
Nota anche: l'endpoint per la generazione / convalida di token deve essere limitato in modo che non possa essere forzato bruto.