https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-2fa-server-with-kotlin-and-ktor/kotlin_ktor_2fa_1200x600_white.png

Construire un serveur 2FA avec Kotlin et Ktor

Publié le January 20, 2021

Temps de lecture : 7 minutes

Dans ce tutoriel, vous écrirez un serveur qui fournit une API pour Authentification à deux facteurs (2FA). Cette API permettra aux clients de bureau, aux clients mobiles et aux clients web d'utiliser l'authentification à deux facteurs.

Pour construire l'application, vous utiliserez l'outil Kotlin et le langage Ktor, un framework asynchrone pour la création de microservices et d'applications web.

Le code source complet est disponible sur GitHub.

Conditions préalables

Pour suivre ce tutoriel, vous aurez besoin de :

  • IntelliJ IDEA IDE installé (payant ou gratuit, édition communautaire).

  • Ktor pour IntelliJ IDEA. Ce plugin vous permet de créer un projet Ktor à l'aide d'un nouvel assistant de projet. Ouvrir IntelliJ IDEAet allez dans Préférencespuis Pluginset installez un plugin Ktor à partir de la place de marché.

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Créer un projet Ktor

  • Ouvrir IntelliJ IDEApuis allez à Fichier > Nouveau > Projet.

  • Dans le Nouveau projet sélectionnez le projet Ktor sur le côté gauche et appuyez sur le bouton suivant et appuyez sur le bouton Suivant.

  • Dans l'écran suivant, laissez les valeurs par défaut et appuyez sur le bouton suivant et appuyez sur le bouton Suivant.

  • Dans l'écran final, entrez ktor-2fa-server comme nom de l'application et appuyez sur le bouton terminer et appuyez sur le bouton Terminer.

Vous avez créé un projet d'application Ktor.

Premier point d'aboutissement

Ouvrez le fichier src/Application.kt et ajoutez un nouveau routing pour vérifier que l'application fonctionne :

fun Application.module(testing: Boolean = false) {
    routing {
        get("/") {
            call.respondText("2FA app is working", ContentType.Text.Html)
        }
    }
}

Dans ce tutoriel, tout le code de l'application Ktor sera stocké dans le fichier Application.kt dans le fichier

Cliquer sur la flèche verte à côté de la fonction main pour exécuter l'application (cela créera une nouvelle configuration d'exécution dans l'IDE) :

Run app

Naviguez vers http://localhost:8080/ dans votre navigateur pour vérifier si l'application fonctionne correctement - "2FA app is working" devrait s'afficher :

App is working

Définir le mode de développement

L'activation du mode développement permet à l'application Ktor d'afficher des informations de débogage plus détaillées dans l'IDE, telles que la pile d'appels. Cela facilitera le développement et le diagnostic des problèmes.

Ouvrez le fichier resources/application.conf et ajoutez development = true:

ktor {
    development = true

    ...

Ajouter des dépendances

SDK Java de Vonage

Le langage Kotlin offre interopérabilité avec Javace qui vous permet d'appeler du code Java à partir du code Kotlin afin d'utiliser le SDK Java de Vonage de Vonage pour le projet Kotlin/Ktor.

Ouvrez le fichier build.gradle et ajoutez la dépendance suivante :

dependencies {

    ...

    implementation 'com.vonage:client:6.1.0'
}

Sérialisation

Vous utiliserez JSON comme format de données pour communiquer avec les clients. Vous sérialiserez les objets Kotlin en utilisant la Sérialisation Kotlin.

Ouvrez le fichier build.gradle et ajoutez les dépendances suivantes :

dependencies {

    ...
    
    implementation "io.ktor:ktor-serialization:$ktor_version"
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}

La bibliothèque de sérialisation Kotlin utilise le prétraitement (au moment de la compilation), vous devez donc ajouter le plugin org.jetbrains.kotlin.plugin.serialization Gradle. Au moment de l'écriture de cet article, Ktor utilise utilise l'ancienne façon d'appliquer les plugins GradleNous devons donc la remplacer par la nouvelle configuration.

Ouvrez le fichier build.gradle et supprimez les plugins :

apply plugin: 'kotlin'
apply plugin: 'application'

Retirer le mainClassName:

mainClassName = "io.ktor.server.netty.EngineMain"

Retirer le classpath:

dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}

Ajoutez des plugins en utilisant la nouvelle syntaxe Gradle, juste en dessous de buildscript bloc :

buildscript {
    // ...
}

plugins {
    id "java"
    id "org.jetbrains.kotlin.jvm" version "$kotlin_version"
    id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}

Après toutes les modifications, le fichier build.gradle devrait ressembler à ceci :

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

plugins {
    id "java"
    id "org.jetbrains.kotlin.jvm" version "$kotlin_version"
    id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}

group 'com.example'
version '0.0.1'

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "io.ktor:ktor-server-netty:$ktor_version"
    implementation "ch.qos.logback:logback-classic:$logback_version"
    testImplementation "io.ktor:ktor-server-tests:$ktor_version"

    implementation 'com.vonage:client:6.1.0'
    implementation "io.ktor:ktor-serialization:$ktor_version"
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}

Les kotlin_version et ktor_version sont définies dans le fichier gradle.properties fichier.

Pour activer la sérialisation, le convertisseur JSON doit être activé pour l'application Ktor. Ouvrez le fichier Application.kt et ajoutez un bloc install à l'intérieur de Application.module fonction :

fun Application.module(testing: Boolean = false) {

    install(ContentNegotiation) {
        json()
    }
    
    // ...
}

L'IDE marquera toutes les classes et extensions dont l'importation est manquante avec la couleur rouge. Survolez le nom de la classe ou de la méthode, attendez qu'une fenêtre apparaisse, et sélectionnez import... pour ajouter l'importation de la classe et corriger l'erreur.

Créer une application Vonage

Une application Vonage fournira des capacités 2FA pour l'API. Créez une application Vonage dans la section tableau de bord. Cliquez sur l'icône Créer une nouvelle application entrez un nom et cliquez sur le bouton Générer une nouvelle application et cliquez sur le bouton Générer une nouvelle application.

Aller à paramètres et notez API key et API secret.

Initialiser le client Vonage

Ajouter la propriété client à l'intérieur de la fonction Application.module pour initialiser un client Vonage :

fun Application.module(testing: Boolean = false) {

    val client: VonageClient = VonageClient.builder()
        .apiKey("API_KEY")
        .apiSecret("API_SECRET")
        .build()

    install(ContentNegotiation) {
        json()
    }

    // ...
}

Remplacer API_KEY et API_SECRET en utilisant les valeurs du tableau de bord.

NOTE : en cours de production API_KEY et API_SECRET devraient être récupérées à partir des variables d'environnement.

Fonctionnalité de l'API

Vous allez créer deux points d'accès à l'API :

  • verifyNumber - le client accède d'abord à ce point de terminaison pour lancer le processus de vérification en traitant le numéro de téléphone à vérifier.

  • verifyCode - après avoir reçu le code (par SMS ou appel vocal), le client envoie le code et l'application effectue un contrôle 2FA pour déterminer si le client est vérifié.

Créer le point de terminaison API verifyNumber

Définir un nouveau gestionnaire de route, get("/verifyNumber")à l'intérieur du bloc routing de la fonction Application.module fonction :

fun Application.module(testing: Boolean = false) {

    // ...

    routing {
        get("/") {
            call.respondText("2FA app is working", ContentType.Text.Html)
        }
        get("/verifyNumber") {
            // ...
        }
    }
}

Le code contenu dans le get("/verifyNumber") sera exécuté lorsque le client fera un appel à l'URL. http://localhost:8080/verifyNumber URL.

Le point de terminaison verifyNumber contiendra la logique suivante :

  • récupérer phoneNumber à partir de la chaîne de requête (http://localhost:8080/verifyNumber?phoneNumber=1234)

  • démarrer la vérification 2FA à l'aide du SDK de Vonage

  • retourner requestId en tant que JSON (dans une application de production, vous stockeriez typiquement l'ID côté serveur)

Ajoutez la logique suivante au get("/verifyNumber") gestionnaire d'itinéraire :

get("/verifyNumber") {
    val phoneNumber = call.parameters["phoneNumber"]
    require(!phoneNumber.isNullOrBlank()) { "phoneNumber is missing" }

    val ongoingVerify = client.verifyClient.verify(phoneNumber, "VONAGE")

    val response = VerifyNumberResponse(ongoingVerify.requestId)
    call.respond(response)
}

Définir une classe VerifyNumberResponse qui sera sérialisée en JSON et renvoyée au client de l'API. Ajoutez le code suivant à la fin du fichier Application.kt à la fin du fichier

@Serializable
data class VerifyNumberResponse(val requestId: String)

Kotlin permet de définir plusieurs membres de haut niveau (classes, propriétés, etc.) dans un seul fichier.

En raison d'un bogue dans le plugin Kotlin, vous devez ajouter manuellement la déclaration d'importation pour l'annotation Serializable manuellement. Ajoutez le code suivant au début du fichier, juste en dessous de la dernière déclaration d'importation :

import kotlinx.serialization.Serializable

Au lieu d'utiliser la vérification intégrée de Vonage, vous pouvez générer le code vous-même et envoyer un SMS à l'aide du SDK Java de Vonage. Cependant, le mécanisme de vérification de Vonage offre un moyen facile d'utiliser des flux de travail plus complexes. flux de travailPar exemple, le flux de travail par défaut passera un appel téléphonique et lira le code à l'utilisateur si le client n'a pas fourni de code SMS dans un délai spécifique.

Créer le point final de l'API verifyCode

Définir un nouveau gestionnaire de route, get("/verifyCode")à l'intérieur du bloc routing de la fonction Application.module fonction :

fun Application.module(testing: Boolean = false) {

    // ...

    routing {
        // ...
        get("/verifyCode") {
            // ...
        }
    }
}

Le point de terminaison verifyCode contiendra la logique suivante :

  • récupérer code de la chaîne de requête (code sera transmis à l'utilisateur après avoir atteint le point de terminaison verifyNumber point de terminaison)

  • récupérer un paramètre de vérification requestId à partir de la chaîne de requête (valeur récupérée à partir du point de terminaison verifyNumber point final)

  • Verify code using Vonage SDK

  • renvoyer au client l'état de la vérification

Ajoutez la logique suivante au get("/verifyCode") gestionnaire d'itinéraire :

get("/verifyCode") {
    val code = call.parameters["code"]
    val requestId = call.parameters["requestId"]

    val checkResponse = client.verifyClient.check(requestId, code)
    println(checkResponse.status)

    val status = if(checkResponse.status == VerifyStatus.OK) {
        "OK"
    } else {
        "ERROR: ${checkResponse.status}"
    }

    val response = VerifyCodeResponse(status)
    call.respond(response)
}

Définir une classe VerifyCodeResponse qui sera sérialisée en JSON et renvoyée au client de l'API. Ajoutez le code suivant à la fin du fichier Application.kt à la fin du fichier

@Serializable
data class VerifyCodeResponse(val status: String)

Après toutes les modifications, Application.kt devrait ressembler à ceci :

package com.example

import com.vonage.client.VonageClient
import com.vonage.client.verify.VerifyStatus
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.serialization.*
import kotlinx.serialization.Serializable

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {

    val client: VonageClient = VonageClient.builder()
        .apiKey("API_KEY")
        .apiSecret("API_KEY")
        .build()

    install(ContentNegotiation) {
        json()
    }

    routing {
        get("/") {
            call.respondText("2FA app is working", ContentType.Text.Html)
        }
        get("/verifyNumber") {
            val phoneNumber = call.parameters["phoneNumber"]
            require(!phoneNumber.isNullOrBlank()) { "phoneNumber is missing" }

            val ongoingVerify = client.verifyClient.verify(phoneNumber, "VONAGE")
            val response = VerifyNumberResponse(ongoingVerify.requestId)
            call.respond(response)
        }
        get("/verifyCode") {
            val code = call.parameters["code"]
            val requestId = call.parameters["requestId"]

            val checkResponse = client.verifyClient.check(requestId, code)
            println(checkResponse.status)

            val status = if(checkResponse.status == VerifyStatus.OK) {
                "OK"
            } else {
                "ERROR: ${checkResponse.status}"
            }

            val response = VerifyCodeResponse(status)
            call.respond(response)
        }
    }
}

@Serializable
data class VerifyNumberResponse(val requestId: String)

@Serializable
data class VerifyCodeResponse(val status: String)

Utiliser l'API

La mise en œuvre de l'API est terminée, nous allons donc la tester.

Tout client peut utiliser l'API, y compris les clients de bureau et les clients mobiles, mais vous effectuerez des tests simples à l'aide d'un navigateur web.

Lancez l'application Ktor.

Remplacez PHONE_NUMBER par un numéro de téléphone réel et ouvrez l'URL suivante dans le navigateur :

http://localhost:8080/verifyNumber?phoneNumber=PHONE_NUMBER

Les numéros de téléphone de Vonage sont en E.164 les "+" et les "-" ne sont pas valables. Veillez à préciser l'indicatif de votre pays lorsque vous saisissez votre numéro, par exemple, US : 14155550100 et UK : 447700900001

En tant qu'utilisateur testvous ne pourrez envoyer des SMS et passer des appels vocaux qu'au numéro auquel vous vous êtes inscrit et à 4 autres numéros d'essai de votre choix (vous pouvez recharger votre compte Vonage pour supprimer cette restriction).

Vous devriez recevoir un SMS avec un code et voir une réponse similaire :

{"requestId":"9ac76db7971b4ea4a49f2e061432c6fe"}

Composer une deuxième demande. Remplacez REQUEST_ID par la valeur renvoyée par le serveur (dans l'exemple ci-dessus, il s'agit de 9ac76db7971b4ea4a49f2e061432c6fe) et remplacez CODE par le code de vérification reçu :

http://localhost:8080/verifyCode?requestId=REQUEST_ID&code=CODE

Si le numéro de téléphone du client est vérifié, la réponse suivante devrait s'afficher :

{"status":"OK"}

Vous utilisez un flux de vérification Vonage par défaut (/verify/verify-v1/guides/workflows-and-events), donc si vous n'entrez pas le code dans les 125 secondes, vous recevrez l'appel vocal lisant le code.

Pour en savoir plus

Vous pouvez trouver le code présenté dans ce tutoriel sur le site Github.

Vous trouverez ci-dessous quelques autres tutoriels que nous avons rédigés sur l'utilisation de nos services avec Go :

Si vous avez des questions, des conseils ou des idées que vous souhaitez partager avec la communauté, n'hésitez pas à vous rendre sur notre espace de travail Slack de la communauté. J'aimerais que tous ceux qui ont mis en œuvre ce tutoriel me fassent part de leurs commentaires et de la manière dont leur projet fonctionne.

Partager:

https://a.storyblok.com/f/270183/384x384/8ae5af43bb/igor-wojda.png
Igor WojdaAnciens de Vonage