
Construir un servidor 2FA con Kotlin y Ktor
Tiempo de lectura: 6 minutos
En este tutorial, usted escribirá un servidor que proporciona una API para Autenticación de dos factores (2FA). Esta API permitirá a los clientes de escritorio, clientes móviles y clientes web utilizar la autenticación de dos factores.
Para crear la aplicación, se utilizará el módulo Kotlin y Ktorun framework asíncrono para crear microservicios y aplicaciones web.
El código fuente completo está disponible en GitHub.
Requisitos previos
Para seguir este tutorial, necesitarás:
IntelliJ IDEA IDE instalado (de pago o gratuito, edición comunitaria).
Ktor para IntelliJ IDEA. Este plugin le permite crear un proyecto Ktor utilizando un nuevo asistente de proyecto. Abrir IntelliJ IDEAy vaya a Preferenciasy luego a Pluginse instale Ktor del mercado.
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.
Crear un proyecto Ktor
Abrir IntelliJ IDEAy vaya a Archivo > Nuevo > Proyecto.
En el Nuevo proyecto seleccione el archivo Ktor en la parte izquierda y pulse el botón Siguiente Siguiente.
En la siguiente pantalla, deje los valores por defecto y pulse la tecla Siguiente Siguiente.
En la pantalla final, introduzca
ktor-2fa-servercomo nombre de la aplicación y pulse el botón Finalizar .
Ha creado un proyecto de aplicación Ktor.
Primer punto final
Abra el src/Application.kt y añada un nuevo routing para verificar que la aplicación funciona:
fun Application.module(testing: Boolean = false) {
routing {
get("/") {
call.respondText("2FA app is working", ContentType.Text.Html)
}
}
}En este tutorial, todo el código de la aplicación Ktor se almacenará en el archivo
Application.ktarchivo.
Haga clic en la flecha verde situada junto a la función main para ejecutar la aplicación (esto creará una nueva configuración de ejecución en el IDE):

Navegue hasta http://localhost:8080/ en su navegador para comprobar si la aplicación funciona correctamente: debería aparecer "2FA app is working":

Modo de desarrollo
Activar el modo de desarrollo permite que la aplicación Ktor muestre información de depuración más detallada en el IDE, como la pila de llamadas. Ayudará con el desarrollo y el diagnóstico de problemas.
Abra el resources/application.conf y añada development = true:
ktor {
development = true
... Añadir dependencias
SDK Java de Vonage
El lenguaje Kotlin proporciona interoperabilidad con Javalo que te permite llamar a código Java desde código Kotlin para que puedas utilizar Vonage Java SDK para el proyecto Kotlin/Ktor.
Abra el archivo build.gradle y añada la siguiente dependencia:
dependencies {
...
implementation 'com.vonage:client:6.1.0'
} Serialización
Utilizarás JSON como formato de datos para comunicarte con los clientes. Serializarás objetos Kotlin usando serialización Kotlin.
Abra el archivo build.gradle y añada las siguientes dependencias:
dependencies {
...
implementation "io.ktor:ktor-serialization:$ktor_version"
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}La librería de serialización de Kotlin utiliza preprocesamiento (en tiempo de compilación), por lo que tienes que añadir el plugin de org.jetbrains.kotlin.plugin.serialization plugin de Gradle. En el momento de escribir este artículo, Ktor está usando usando la antigua forma de aplicar los plugins de Gradlepor lo que tenemos que sustituirla por la nueva configuración.
Abra el archivo build.gradle y elimine los plugins:
apply plugin: 'kotlin'
apply plugin: 'application'Quitar el mainClassName:
mainClassName = "io.ktor.server.netty.EngineMain"Quitar el classpath:
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}Añade plugins usando la nueva sintaxis de Gradle, justo debajo de buildscript bloque:
buildscript {
// ...
}
plugins {
id "java"
id "org.jetbrains.kotlin.jvm" version "$kotlin_version"
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}Después de todas las modificaciones, el archivo build.gradle debería tener este aspecto:
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'
}En
kotlin_versionyktor_versionse definen dentro del archivogradle.propertiesarchivo.
Para habilitar serializatin, el convertidor JSON tiene que estar habilitado para la aplicación Ktor. Abra el archivo Application.kt y añada un bloque install dentro del bloque Application.module función:
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
json()
}
// ...
}El IDE marcará con el color rojo todas las clases y extensiones que tengan import missing. Pase el ratón sobre el nombre de la clase o método, espere a que aparezca una ventana y seleccione
import...para añadir la importación de la clase y corregir el error.
Crear una aplicación de Vonage
Una aplicación de Vonage proporcionará capacidades 2FA para la API. Crea una aplicación de Vonage en el tablero. Haz clic en el botón Crear una nueva aplicación ingresa un nombre y haz clic en el botón Generar nueva aplicación .
Ir a configuración y anota API key y API secret.
Inicializar cliente de Vonage
Añada la propiedad client dentro de la función Application.module para inicializar un cliente de Vonage:
fun Application.module(testing: Boolean = false) {
val client: VonageClient = VonageClient.builder()
.apiKey("API_KEY")
.apiSecret("API_SECRET")
.build()
install(ContentNegotiation) {
json()
}
// ...
}Sustituya API_KEY y API_SECRET utilizando los valores del cuadro de mandos.
NOTA: en producción API_KEY y API_SECRET deben recuperarse de variables de entorno.
Funcionalidad API
Construirá dos puntos finales de API:
verifyNumber- el cliente accederá primero a este punto final para iniciar el proceso de verificación procesando el número de teléfono que debe verificarse.verifyCode- tras recibir el código (por SMS o llamada de voz), el cliente enviará el código y la aplicación realizará una comprobación 2FA para determinar si el cliente está verificado.
Creación del punto final de la API verifyNumber
Definir un nuevo manejador de ruta, get("/verifyNumber")dentro del bloque routing de la función Application.module función:
fun Application.module(testing: Boolean = false) {
// ...
routing {
get("/") {
call.respondText("2FA app is working", ContentType.Text.Html)
}
get("/verifyNumber") {
// ...
}
}
}El código dentro del
get("/verifyNumber")manejador de ruta se ejecutará cuando el cliente haga una llamada a lahttp://localhost:8080/verifyNumberURL.
El punto final verifyNumber endpoint contendrá la siguiente lógica:
recuperar
phoneNumberde la cadena de consulta (http://localhost:8080/verifyNumber?phoneNumber=1234)iniciar la verificación 2FA con el SDK de Vonage
devuelve
requestIdcomo JSON (en una aplicación de producción, normalmente almacenaría el ID en el lado del servidor)
Añada la siguiente lógica al get("/verifyNumber") controlador de ruta:
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)
}Define una VerifyNumberResponse que se serializará a JSON y se devolverá al cliente de la API. Añada el siguiente código al final de Application.kt del archivo:
@Serializable
data class VerifyNumberResponse(val requestId: String)Kotlin permite definir múltiples miembros de nivel superior (clases, propiedades, etc.) dentro de un único archivo.
Debido a un error en el plugin de Kotlin, es necesario añadir la sentencia import para la anotación Serializable manualmente. Añade el siguiente código en la parte superior del archivo, justo debajo de la última sentencia import:
import kotlinx.serialization.SerializableEn lugar de usar la verificación incorporada de Vonage, podrías generar el código tú mismo y enviar un SMS usando Vonage Java SDK. Sin embargo, el mecanismo de verificación de Vonage ofrece una manera sencilla de utilizar flujos de trabajopor ejemplo: el flujo de trabajo predeterminado realizará una llamada telefónica y leerá el código al usuario si el cliente no proporcionó el código SMS dentro de un período específico.
Crear el punto final de la API verifyCode
Definir un nuevo manejador de ruta, get("/verifyCode")dentro del bloque routing de la función Application.module función:
fun Application.module(testing: Boolean = false) {
// ...
routing {
// ...
get("/verifyCode") {
// ...
}
}
}El punto final verifyCode endpoint contendrá la siguiente lógica:
recuperar
codede la cadena de consulta (codese entregará al usuario después de pulsar el botónverifyNumberpunto final)recuperar un parámetro de verificación
requestIdde la cadena de consulta (valor recuperado deverifyNumberendpoint)Verifica el código usando Vonage SDK
devolver al cliente el estado de verificación
Añada la siguiente lógica al get("/verifyCode") controlador de ruta:
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)
}Define una VerifyCodeResponse que se serializará a JSON y se devolverá al cliente de la API. Añada el siguiente código al final de Application.kt del archivo:
@Serializable
data class VerifyCodeResponse(val status: String)Después de todas las modificaciones, Application.kt el archivo debería verse así:
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) Utilizar la API
La implementación de la API está completa, así que vamos a probarla.
Cualquier cliente puede utilizar la API, incluidos los clientes de escritorio y móviles, pero usted realizará pruebas sencillas utilizando un navegador web.
Inicie la aplicación Ktor.
Sustituya PHONE_NUMBER por un número de teléfono real y abra la siguiente URL en el navegador:
http://localhost:8080/verifyNumber?phoneNumber=PHONE_NUMBERLos números de teléfono de Vonage están en E.164 '+' y '-' no son válidos. Asegúrate de especificar el código de tu país al ingresar tu número, por ejemplo, US: 14155550100 y Reino Unido: 447700900001
Como usuario de pruebasólo podrás enviar SMS y realizar llamadas de voz al número con el que te registraste y a otros 4 números de prueba de tu elección (puedes recargar tu cuenta de Vonage para eliminar esta restricción).
Deberías recibir un SMS con un código y ver una respuesta similar:
{"requestId":"9ac76db7971b4ea4a49f2e061432c6fe"}Redacte una segunda solicitud. Sustituya REQUEST_ID por el valor devuelto por el servidor (en el ejemplo anterior, es 9ac76db7971b4ea4a49f2e061432c6fe) y sustituya CODE por el código de verificación recibido:
http://localhost:8080/verifyCode?requestId=REQUEST_ID&code=CODE
Si el número de teléfono del cliente está verificado, debería ver la siguiente respuesta:
{"status":"OK"}Estás usando un flujo de trabajo de verificación predeterminado de Vonage (/verify/verify-v1/guides/workflows-and-events), por lo que si no ingresas el código dentro de los 125 segundos, recibirás la llamada de voz que lee el código.
Lecturas complementarias
Puede encontrar el código que se muestra en este tutorial en la página de Github.
A continuación encontrarás otros tutoriales que hemos escrito sobre el uso de nuestros servicios con Go:
Si tienes alguna pregunta, consejo o idea que quieras compartir con la comunidad, no dudes en entrar en nuestro espacio de trabajo espacio de trabajo Slack de la comunidad. Me encantaría saber de alguien que haya implementado este tutorial y cómo funciona su proyecto.
