https://a.storyblok.com/f/270183/91414/769438e534/nexmo-2fa_android.jpg

Adicione a autenticação de dois fatores a aplicativos Android com a Verify API da Nexmo

Publicado em May 12, 2021

Tempo de leitura: 5 minutos

Autenticação de dois fatores (2FA) adiciona uma camada extra de segurança para usuários que acessam informações confidenciais. Neste tutorial, abordaremos como implementar a autenticação de dois fatores para o número de telefone de um usuário usando os endpoints da Verify API da Nexmo.

Depois de ler a postagem do blog sobre como configurar um servidor para usar o Nexmo Verify , você já está pronto para configurar um aplicativo Android para se comunicar com o servidor.

Pré-requisitos

Este aplicativo terá apenas duas dependências: Retrofit para fazer chamadas de rede e Moshi para serializar e desserializar JSON.

O aplicativo precisará realizar algumas tarefas. Armazenar um requestId para que uma solicitação de verificação possa ser cancelada ou concluída. Além disso, fazer uma chamada de rede para três endpoints:

  • Iniciar uma verificação

  • Verificar um código de verificação

  • Cancelar uma solicitação de verificação

Para começar, criei um aplicativo de demonstração simples com uma tela de login que solicita o endereço de e-mail, a senha e o número de telefone do usuário para autenticação de dois fatores (2FA). Clone o repositório a seguir e acesse o branch “getting started”:

git clone git@github.com:nexmo-community/verify-android-example.git
cd verify-android-example
git checkout getting-started

Integração com o servidor proxy

Na postagem do blog sobre como configurar um servidor proxy para a Verify API, abordamos três endpoints:

  • Faça uma solicitação de verificação.

  • Verifique o código de verificação.

  • Cancelar uma solicitação de verificação.

Então, precisaremos fazer com que nosso aplicativo envie POSTs para esses três endpoints. Para isso, vamos usar o Retrofit e o Moshi.

Realização de solicitações de rede

O arquivo build.gradle deve incluir as seguintes dependências: Retrofit para comunicação em rede, Moshi para análise de JSON e OkHttp para registro de logs.

//app/build.gradle
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'

Podemos começar a usá-los para nos conectarmos à nossa API proxy.

Primeiro, vamos configurar uma interface para os três endpoints que nosso aplicativo precisa acessar:

public interface VerifyService {

    @POST("request")
    Call<VerifyResponse> request(@Body PhoneNumber phoneNumber);

    @POST("check")
    Call<CheckVerifyResponse> check(@Body VerifyRequest verifyRequest);

    @POST("cancel")
    Call<CancelVerifyResponse> cancel(@Body RequestId requestId);
}

Não vou abordar cada um dos modelos de respostas, mas você pode vê-los com mais detalhes aqui. Para os fins deste tutorial, adaptei a estrutura dos modelos ao JSON que Verify API espera.

Agora que já existe uma interface de endpoints, podemos escrever um VerifyUtil que instanciará o Retrofit e fará as solicitações de rede:

public class VerifyUtil {

    private final Retrofit retrofit;
    private VerifyService verifyService;
    private static VerifyUtil instance = null;

    private VerifyUtil() {
        //Use OkHttp for logging
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

        retrofit = new Retrofit.Builder()
                .client(client)
                //change this url to your own proxy API url
                .baseUrl("https://nexmo-verify.glitch.me")
                .addConverterFactory(MoshiConverterFactory.create())
                .build();

        verifyService = retrofit.create(VerifyService.class);
    }

    public static VerifyUtil getInstance() {
        if (instance == null) {
            instance = new VerifyUtil();
        }
        return instance;
    }

    public VerifyService getVerifyService() {
        return verifyService;
    }

    public Retrofit getRetrofit() {
        return retrofit;
    }
}

Eu criei VerifyUtil um singleton que pode ser chamado a partir de qualquer Activity.

Vamos começar fazendo algumas alterações na nossa LoginActivity. A signInBtn já possui um OnClickListener que chama start2FA() , então podemos adicionar a esse método.

Atividade de login

private void start2FA(final String phone) {
    //clear out the previous error (if any) that was shown.
    errorTxt.setText(null);
    //start the verification request. Wrap `String phone` in a `PhoneNumber` class so it is correctly serialized into JSON
    Call<VerifyResponse> request = VerifyUtil.getInstance().getVerifyService().request(new PhoneNumber(phone));
    request.enqueue(new Callback<VerifyResponse>() {
        @Override
        public void onResponse(Call<VerifyResponse> call, Response<VerifyResponse> response) {
            if (response.isSuccessful()) {
                //parse the response
                VerifyResponse requestVerifyResponse = response.body();
                storeResponse(phone, requestVerifyResponse);
                startActivity(new Intent(LoginActivity.this, PhoneNumberConfirmActivity.class));
            } else {
                //if the HTTP response is 4XX, Retrofit doesn't pass the response to the `response.body();`
                //So we need to convert the `response.errorBody()` to the `VerifyResponse`
                Converter<ResponseBody, VerifyResponse> errorConverter = VerifyUtil.getInstance().getRetrofit().responseBodyConverter(VerifyResponse.class, new Annotation[0]);
                try {
                    VerifyResponse verifyResponse = errorConverter.convert(response.errorBody());
                    Toast.makeText(LoginActivity.this, "Error Will Robinson!", Toast.LENGTH_LONG).show();
                    errorTxt.setText(verifyResponse.getErrorText());
                } catch (IOException e) {
                    Log.e(TAG, "onResponse: ", e);
                }
            }
        }

        @Override
        public void onFailure(Call<VerifyResponse> call, Throwable t) {
            Toast.makeText(LoginActivity.this, "Error Will Robinson!", Toast.LENGTH_LONG).show();
            Log.e(TAG, "onFailure: ", t);
        }
    });
}

Sempre que alguém clicar no botão “Entrar”, o aplicativo enviará o número de telefone para a API proxy que configuramos no post anterior. Este aplicativo ignorará qualquer informação nas telas de e-mail ou senha, já que se trata de uma prova de conceito.

Se houver um erro na solicitação, a API proxy enviará uma 400 resposta e vamos tratá-la no else bloco do onResponse() callback. Se ocorrer qualquer outro erro, o servidor proxy responderá com um 500 e o aplicativo poderá lidar com o erro no onFailure() callback.

Se a solicitação for bem-sucedida, o aplicativo armazenará o número de telefone enviado pelo usuário e o responseId retornado pelo servidor proxy; em seguida, o aplicativo iniciará o PhoneNumberConfirmActivity para que o usuário possa inserir seu código. É importante armazenar o número de telefone e o ID da solicitação, pois esses campos são necessários para cancelar ou verificar o status de qualquer solicitação de verificação. E, embora um usuário possa se lembrar do próprio número de telefone para cancelar ou verificar o status de uma verificação, não se pode esperar que ele se lembre de uma sequência de caracteres gerada aleatoriamente para o ID da solicitação. Portanto, quando o aplicativo fizer uma solicitação de verificação, armazenaremos o requestId em SharedPreferences para recuperá-lo mais tarde, caso o usuário coloque o aplicativo em segundo plano ou a atividade seja reiniciada.

Atividade de Confirmação de Número de Telefone

Assim que o usuário digitar o número de telefone e o servidor responder com um 200, o aplicativo iniciará a PhoneNumberConfirmActivity. Já iniciei a atividade no getting-started ramo

A estrutura básica da atividade já está pronta. Agora basta usar o requestId e o número de telefone para confirmar o código PIN ou cancelar a verificação.

Primeiro, vamos começar obtendo o número de telefone e o requestId de SharedPreferences. Precisaremos do número de telefone e requestId para confirmar o código de verificação ou cancelar o processo de verificação.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_phone_number_confirm);

    //Get user's phone number and request ID from SharedPreferences
    SharedPreferences sharedPref = getSharedPreferences(TWO_FACTOR_AUTH, Context.MODE_PRIVATE);
    String phoneNumber = sharedPref.getString(PHONE_NUMBER, null);
    Log.d(TAG, "phone: " + phoneNumber);
    requestId = sharedPref.getString(phoneNumber, null);
    Log.d(TAG, "request id: " + requestId);

    //Set up rest of views on OnClickListeners...
}

Então, assim que o usuário clicar no botão “Confirmar”, o aplicativo precisa enviar o código PIN e a requestId para a API proxy. Podemos configurar o evento de clique do botão “Confirmar” OnClickListener para acionar essa solicitação.

private void confirmCode(String code) {
    //clear out the previous error (if any) that was shown.
    verifyTxt.setText(null);
    //Confirm the verification code. Wrap `String code` and requestId in a `VerifyRequest` class so it is correctly serialized into JSON
    VerifyUtil.getInstance().getVerifyService().check(new VerifyRequest(code, requestId)).enqueue(new Callback<CheckVerifyResponse>() {
        @Override
        public void onResponse(Call<CheckVerifyResponse> call, Response<CheckVerifyResponse> response) {
            if (response.isSuccessful()) {
                verifyTxt.setText("Verified!");
                Toast.makeText(PhoneNumberConfirmActivity.this, "Verified!", Toast.LENGTH_LONG).show();
            } else {
                //if the HTTP response is 4XX, Retrofit doesn't pass the response to the `response.body();`
                //So we need to convert the `response.errorBody()` to a `CheckVerifyResponse`
                Converter<ResponseBody, CheckVerifyResponse> errorConverter = VerifyUtil.getInstance().getRetrofit().responseBodyConverter(CheckVerifyResponse.class, new Annotation[0]);
                try {
                    CheckVerifyResponse checkVerifyResponse = errorConverter.convert(response.errorBody());
                    verifyTxt.setText(checkVerifyResponse.getErrorText());
                    Toast.makeText(PhoneNumberConfirmActivity.this, "Error Will Robinson!", Toast.LENGTH_LONG).show();
                } catch (IOException e) {
                    Log.e(TAG, "onResponse: ", e);
                }
            }
        }

        @Override
        public void onFailure(Call<CheckVerifyResponse> call, Throwable t) {
          Toast.makeText(PhoneNumberConfirmActivity.this, "Error Will Robinson!", Toast.LENGTH_LONG).show();
          Log.e(TAG, "onFailure: ", t);
        }
  });
}

A solicitação de rede para confirmar o código PIN é semelhante à solicitação feita anteriormente para iniciar o processo de verificação. Se tudo correr bem, o servidor responderá com um 200 OK e podemos informar ao usuário que ele está autenticado. Se o servidor responder com um 400 ou 500 , então podemos verificar o errorText da resposta e alertar o usuário sobre o problema, seja com uma notificação pop-up ou exibindo o erro no verifyTxt TextView que criei.

Pode haver momentos em que os usuários queiram cancelar uma solicitação de verificação. Isso pode ocorrer porque eles digitaram o número de telefone errado, querem fazer login com outra conta ou simplesmente não querem se verificar neste momento. O aplicativo precisa lidar com essa situação; por isso, vamos adicionar um botão “Cancelar” à nossa atividade e configurá-lo para enviar uma solicitação de cancelamento pela rede. Já adicionei o botão “Cancelar” e inseri um OnClickListener com um callback de cancelRequest(); agora basta adicionar o código de rede a esse método.

private void cancelRequest() {
    //clear out the previous error (if any) that was shown.
    verifyTxt.setText(null);
    //Cancel the verification request
    VerifyUtil.getInstance().getVerifyService().cancel(new RequestId(requestId)).enqueue(new Callback<CancelVerifyResponse>() {
        @Override
        public void onResponse(Call<CancelVerifyResponse> call, Response<CancelVerifyResponse> response) {
            if (response.isSuccessful()) {
                Toast.makeText(PhoneNumberConfirmActivity.this, "Cancelled!", Toast.LENGTH_LONG).show();
                finish();
            } else {
                //if the HTTP response is 4XX, Retrofit doesn't pass the response to the `response.body();`
                //So we need to convert the `response.errorBody()` to a `CancelVerifyResponse`
                Converter<ResponseBody, CancelVerifyResponse> errorConverter = VerifyUtil.getInstance().getRetrofit().responseBodyConverter(CancelVerifyResponse.class, new Annotation[0]);
                try {
                    CancelVerifyResponse cancelVerifyResponse = errorConverter.convert(response.errorBody());
                    verifyTxt.setText(cancelVerifyResponse.getErrorText());
                    Toast.makeText(PhoneNumberConfirmActivity.this, "Error Will Robinson!", Toast.LENGTH_LONG).show();
                } catch (IOException e) {
                    Log.e(TAG, "onResponse: ", e);
                }
            }
        }

        @Override
        public void onFailure(Call<CancelVerifyResponse> call, Throwable t) {
            Toast.makeText(PhoneNumberConfirmActivity.this, "Error Will Robinson!", Toast.LENGTH_LONG).show();
            Log.e(TAG, "onFailure: ", t);
        }
    });
}

Conclusão

Agora já implementamos todos os endpoints necessários para seguir um fluxo de verificação. Se você quiser ver o resultado final desse aplicativo para Android, o código-fonte está no finished ramo no GitHub.

Próximos passos

Se desejar, você pode implementar os demais endpoints da Verify API. Observe que isso exigirá que você adicione mais endpoints no servidor proxy da API. Você também pode adicionar endpoints adicionais para abranger a Number Insight API. Isso também exigirá que você adicione mais endpoints no servidor proxy da API. Há também uma versão para iOS desta publicação. Leia mais com nosso especialista em desenvolvimento, Eric Giannini.

Riscos/Isenção de responsabilidade

Talvez seja recomendável verificar o certificado SSL das respostas da sua API proxy para garantir que o aplicativo cliente não esteja sujeito a um ataque MITM. Para mais detalhes, acesse a documentação para desenvolvedores do Android.

Compartilhar:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Chris GuzmanEx-funcionários da Vonage

Chris é Developer Advocate na Nexmo, onde ajuda desenvolvedores a utilizar sua plataforma global de comunicações. Quando não está em conferências, é possível encontrá-lo viajando pelo mundo.