https://a.storyblok.com/f/270183/95989/a660476370/kotlin_ktor_2fa_1200x600_white.png

Crie um servidor de autenticação de duas etapas (2FA) com Kotlin e Ktor

Publicado em January 20, 2021

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-server como 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.kt arquivo.

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):

Run app

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”:

App is working

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_version e ktor_version propriedades são definidas dentro do gradle.properties arquivo.

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 a http://localhost:8080/verifyNumber URL.

O verifyNumber endpoint conterá a seguinte lógica:

  • recuperar phoneNumber parâmetro da string de consulta (http://localhost:8080/verifyNumber?phoneNumber=1234)

  • Iniciar a verificação por 2FA usando o SDK da Vonage

  • retornar requestId como 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.Serializable

Em 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 code parâmetro da string de consulta (code será entregue ao usuário após acessar o verifyNumber ponto de extremidade)

  • recuperar um parâmetro de verificação requestId da string de consulta (valor recuperado do verifyNumber ponto 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_NUMBER

Os 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.

Compartilhar:

https://a.storyblok.com/f/270183/384x384/8ae5af43bb/igor-wojda.png
Igor WojdaEx-funcionários da Vonage