
Crie um servidor de autenticação de duas etapas (2FA) com Kotlin e Ktor
Tempo de leitura: 6 minutos
Neste tutorial, você criará um servidor que fornece uma API para autenticação de dois fatores (2FA). Essa API permitirá que clientes de desktop, clientes móveis e clientes web utilizem a autenticação de dois fatores.
Para compilar o aplicativo, você usará o linguagem Kotlin e Ktor, uma estrutura assíncrona para a criação de microsserviços e aplicativos web.
O código-fonte completo está disponível no GitHub.
Pré-requisitos
Para acompanhar este tutorial, você precisará de:
IntelliJ IDEA IDE instalado (pago ou gratuito, edição comunitária).
Ktor para o IntelliJ IDEA. Este plug-in permite criar um projeto Ktor usando um assistente de novo projeto. Abra o IntelliJ IDEA, vá para Preferências, depois Plug-inse instale um plug-in plug-in do marketplace.
Criar um projeto Ktor
Abrir IntelliJ IDEAe, em seguida, vá para Arquivo > Novo > Projeto.
No janela “Novo Projeto” , selecione o Ktor no lado esquerdo e pressione o botão Próximo .
Na próxima tela, mantenha os valores padrão e pressione a tecla Próximo .
Na tela final, digite
ktor-2fa-servercomo nome do aplicativo e pressione o botão Concluir .
Você criou um projeto de aplicativo Ktor.
Primeiro desfecho
Abra o src/Application.kt arquivo e adicione um novo routing para verificar se o aplicativo está funcionando:
fun Application.module(testing: Boolean = false) {
routing {
get("/") {
call.respondText("2FA app is working", ContentType.Text.Html)
}
}
}Neste tutorial, todo o código do aplicativo Ktor será armazenado no
Application.ktarquivo.
Clique na seta verde ao lado da main função para executar o aplicativo (isso criará uma nova configuração de execução no IDE):

Acesse http://localhost:8080/ no seu navegador para testar se o aplicativo está funcionando corretamente — deve ser exibida a mensagem “O aplicativo 2FA está funcionando”:

Ativar o modo de desenvolvimento
Ativar o modo de desenvolvimento permite que o aplicativo Ktor exiba informações de depuração mais detalhadas no IDE, como a pilha de chamadas. Isso ajudará no desenvolvimento e no diagnóstico de problemas.
Abra o resources/application.conf arquivo e adicione development = true:
ktor {
development = true
... Adicionar dependências
SDK Java da Vonage
A linguagem Kotlin oferece interoperabilidade com Java, o que permite chamar código Java a partir de código Kotlin, para que você possa usar o SDK Java da Vonage no projeto Kotlin/Ktor.
Abra o build.gradle arquivo e adicione a seguinte dependência:
dependencies {
...
implementation 'com.vonage:client:6.1.0'
} Serialização
Você utilizará JSON como formato de dados para se comunicar com os clientes. Você serializará objetos Kotlin usando a serialização do Kotlin.
Abra o build.gradle arquivo e adicione as seguintes dependências:
dependencies {
...
implementation "io.ktor:ktor-serialization:$ktor_version"
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}A biblioteca de serialização do Kotlin utiliza pré-processamento (em tempo de compilação), portanto, é necessário adicionar o org.jetbrains.kotlin.plugin.serialization plug-in do Gradle. No momento da redação deste artigo, o Ktor está utilizando a forma antiga de aplicar plug-ins do Gradle; por isso, precisamos substituí-la pela nova configuração.
Abra o build.gradle arquivo e remova os plug-ins:
apply plugin: 'kotlin'
apply plugin: 'application'Remova o mainClassName:
mainClassName = "io.ktor.server.netty.EngineMain"Remova o classpath:
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}Adicione plug-ins usando a nova sintaxe do Gradle, logo abaixo do buildscript bloco:
buildscript {
// ...
}
plugins {
id "java"
id "org.jetbrains.kotlin.jvm" version "$kotlin_version"
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}Após todas as modificações, o build.gradle arquivo deve ficar assim:
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'
}O
kotlin_versionektor_versionpropriedades são definidas dentro dogradle.propertiesarquivo.
Para habilitar a serialização, é necessário habilitar o Conversor JSON para o aplicativo Ktor. Abra o Application.kt arquivo e adicione um install bloco dentro da Application.module função:
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
json()
}
// ...
}O IDE destacará em vermelho todas as classes e extensões que não tenham a importação definida. Passe o mouse sobre o nome da classe ou do método, aguarde até que uma janela apareça e selecione
import...para adicionar a importação da classe e corrigir o erro.
Criar uma aplicação da Vonage
Um aplicativo da Vonage fornecerá recursos de autenticação de duas etapas (2FA) para a API. Crie um aplicativo da Vonage no painel de controle. Clique no botão “Criar um novo aplicativo” , digite um nome e clique em Gerar novo aplicativo .
Acesse configurações e anote API key e API secret.
Inicializar o cliente Vonage
Adicione o client propriedade dentro da Application.module função para inicializar um cliente Vonage:
fun Application.module(testing: Boolean = false) {
val client: VonageClient = VonageClient.builder()
.apiKey("API_KEY")
.apiSecret("API_SECRET")
.build()
install(ContentNegotiation) {
json()
}
// ...
}Substituir API_KEY e API_SECRET usando os valores do painel.
NOTA: em produção API_KEY e API_SECRET devem ser recuperadas das variáveis de ambiente.
Funcionalidade da API
Você criará dois endpoints de API:
verifyNumber- o cliente acessará primeiro esse endpoint para iniciar o processo de verificação, processando o número de telefone a ser verificado.verifyCode- após receber o código (por SMS ou chamada de voz), o cliente enviará o código, e o aplicativo realizará uma verificação de autenticação de duas etapas (2FA) para determinar se o cliente está verificado.
Criar um endpoint da API verifyNumber
Defina um novo manipulador de rota, get("/verifyNumber"), dentro do routing bloco da Application.module função:
fun Application.module(testing: Boolean = false) {
// ...
routing {
get("/") {
call.respondText("2FA app is working", ContentType.Text.Html)
}
get("/verifyNumber") {
// ...
}
}
}O código dentro do
get("/verifyNumber")manipulador de rota será executado quando o cliente fizer uma chamada para ahttp://localhost:8080/verifyNumberURL.
O verifyNumber endpoint conterá a seguinte lógica:
recuperar
phoneNumberparâmetro da string de consulta (http://localhost:8080/verifyNumber?phoneNumber=1234)Iniciar a verificação por 2FA usando o SDK da Vonage
retornar
requestIdcomo JSON (em um aplicativo de produção, normalmente você armazenaria o ID no lado do servidor)
Adicione a seguinte lógica ao get("/verifyNumber") manipulador de rota:
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)
}Defina uma VerifyNumberResponse classe que será serializada para JSON e retornada ao cliente da API. Adicione o código a seguir no final do Application.kt arquivo:
@Serializable
data class VerifyNumberResponse(val requestId: String)O Kotlin permite definir vários membros de nível superior (classes, propriedades etc.) em um único arquivo.
Devido a um falha no plug-in do Kotlin, é preciso adicionar manualmente a instrução de importação para a Serializable anotação manualmente. Adicione o código a seguir no início do arquivo, logo abaixo da última instrução de importação:
import kotlinx.serialization.SerializableEm vez de usar a verificação integrada do Vonage, você pode gerar o código por conta própria e enviar um SMS usando o SDK Java do Vonage. No entanto, o mecanismo de verificação do Vonage oferece uma maneira fácil de utilizar fluxos de trabalho mais complexos fluxos de trabalho, por exemplo: o fluxo de trabalho padrão fará uma ligação e lerá o código para o usuário caso o cliente não tenha fornecido o código por SMS dentro de um prazo específico.
Criar um endpoint da API verifyCode
Defina um novo manipulador de rota, get("/verifyCode"), dentro do routing bloco da Application.module função:
fun Application.module(testing: Boolean = false) {
// ...
routing {
// ...
get("/verifyCode") {
// ...
}
}
}O verifyCode endpoint conterá a seguinte lógica:
recuperar
codeparâmetro da string de consulta (codeserá entregue ao usuário após acessar overifyNumberponto de extremidade)recuperar um parâmetro de verificação
requestIdda string de consulta (valor recuperado doverifyNumberponto de extremidade)verificar o código usando o SDK da Vonage
enviar o status da verificação ao cliente
Adicione a seguinte lógica ao get("/verifyCode") manipulador de rota:
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)
}Defina uma VerifyCodeResponse classe que será serializada para JSON e retornada ao cliente da API. Adicione o código a seguir no final do Application.kt arquivo:
@Serializable
data class VerifyCodeResponse(val status: String)Após todas as modificações, Application.kt o arquivo deve ficar assim:
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) Use a API
A implementação da API está concluída, então vamos testá-la.
Qualquer cliente pode usar a API, incluindo aplicativos para desktop e dispositivos móveis, mas você realizará testes simples usando um navegador da web.
Inicie o aplicativo Ktor.
Substitua PHONE_NUMBER por um número de telefone real e abra a seguinte URL no navegador:
http://localhost:8080/verifyNumber?phoneNumber=PHONE_NUMBEROs números de telefone da Vonage estão no formato E.164 ; os sinais “+” e “-” não são válidos. Certifique-se de especificar o código do seu país ao inserir o número; por exemplo, EUA: 14155550100 e Reino Unido: 447700900001
Como usuário em período de teste, você só poderá enviar SMS e fazer chamadas de voz para o número com o qual se cadastrou e para até 4 outros números de teste de sua escolha (você pode recarregar sua Account da Vonage para remover essa restrição).
Você deve receber um SMS com um código e ver uma resposta semelhante a esta:
{"requestId":"9ac76db7971b4ea4a49f2e061432c6fe"}Elabore uma segunda solicitação. Substitua REQUEST_ID pelo valor retornado pelo servidor (no exemplo acima, é 9ac76db7971b4ea4a49f2e061432c6fe) e substitua CODE pelo código de verificação recebido:
http://localhost:8080/verifyCode?requestId=REQUEST_ID&code=CODE
Se o número de telefone do cliente for verificado, você deverá ver a seguinte resposta:
{"status":"OK"}Você está usando um fluxo de trabalho padrão de verificação da Vonage (/verify/verify-v1/guides/workflows-and-events); portanto, se não digitar o código em até 125 segundos, receberá uma ligação de Voice informando o código.
Leitura complementar
Você pode encontrar o código apresentado neste tutorial no Github.
A seguir, apresentamos alguns outros tutoriais que escrevemos, todos relacionados ao uso de nossos serviços com Go:
Se você tiver alguma dúvida, sugestão ou ideia que gostaria de compartilhar com a comunidade, fique à vontade para participar do nosso espaço de trabalho do Slack da Comunidade. Adoraria receber feedback de quem já colocou este tutorial em prática e saber como está o seu projeto.