https://d226lax1qjow5r.cloudfront.net/blog/blogposts/add-two-factor-authentication-to-android-apps-with-nexmos-verify-api-dr/nexmo-2fa_android.jpg

Añada autenticación de dos factores a las aplicaciones Android con Verify API de Nexmo

Publicado el May 12, 2021

Tiempo de lectura: 5 minutos

Autenticación de dos factores (2FA) añade una capa extra de seguridad para los usuarios que acceden a información sensible. En este tutorial, veremos cómo implementar la autenticación de dos factores para el número de teléfono de un usuario con los puntos finales de Verify API de Nexmo.

Después de leer la entrada del blog sobre cómo configurar un servidor para utilizar Nexmo Verify ya está listo para configurar una aplicación Android para conectarse en red con el servidor.

Requisitos

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.

Esta aplicación sólo tendrá dos dependencias: Retrofit para realizar llamadas de red y Moshi para serializar y deserializar JSON.

La aplicación tendrá que hacer algunas cosas. Almacenar un requestId para poder cancelar o completar una solicitud de verificación. Así como hacer una llamada de red a tres puntos finales:

  • Iniciar una verificación

  • Comprobar un código de verificación

  • Anular una solicitud de verificación

Para empezar, he creado una aplicación de demostración sencilla con una pantalla de inicio de sesión en la que se solicita la dirección de correo electrónico, la contraseña y el número de teléfono del usuario para la autenticación de dos factores. autenticación de dos factores (2FA). Clona el siguiente repositorio y navega a la rama de inicio:

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

Integración con el servidor proxy

En la entrada del blog sobre cómo configurar un servidor proxy para Verify API, cubrimos tres puntos finales:

  • Realice una solicitud de verificación.

  • Compruebe un código de verificación.

  • Cancelar una solicitud de verificación.

Así que vamos a necesitar que nuestra aplicación envíe POSTs a esos tres puntos finales. Para ello vamos a utilizar Retrofit y Moshi.

Solicitudes de red

El archivo build.gradle debe incluir las siguientes dependencias. Retrofit para redes, Moshi para análisis JSON y OkHttp para registro.

//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 empezar a utilizarlas para conectarnos en red con nuestra API proxy.

En primer lugar, vamos a configurar una interfaz para los tres puntos finales de nuestra aplicación necesita para golpear:

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);
}

No voy a repasar cada uno de los modelos para las respuestas, pero puede verlos con más detalle aquí. Para el propósito de este tutorial he emparejado la estructura de los modelos con el JSON que espera Verify API.

Ahora que hay una interfaz de puntos finales, podemos escribir un comando VerifyUtil que instanciará Retrofit y hará las peticiones de red:

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;
    }
}

He creado VerifyUtil un singleton que puede ser llamado desde cualquier Activity.

Empecemos haciendo algunos cambios en nuestra LoginActivity. El signInBtn ya tiene un OnClickListener que llama a start2FA() por lo que podemos añadir a ese método.

Actividad de inicio de sesión

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);
        }
    });
}

Cada vez que alguien haga clic en el botón "Iniciar sesión", la aplicación enviará el número de teléfono a la API proxy que hemos configurado en el blog anterior. Esta aplicación ignorará cualquier cosa en las pantallas de correo electrónico o contraseña, ya que esta aplicación es una prueba de concepto.

Si hay un error con la solicitud, la API proxy enviará una 400 y nosotros lo gestionaremos en el bloque else del bloque onResponse() callback. Si hay cualquier otro error, el servidor proxy responderá con un 500 y la aplicación puede gestionar el error en el bloque onFailure() callback.

Si la solicitud tiene éxito, la aplicación almacenará el número de teléfono enviado por el usuario y el número de teléfono devuelto por el servidor proxy. responseId el servidor proxy devuelto, entonces la app iniciará el PhoneNumberConfirmActivity para que el usuario pueda introducir su código. Es importante almacenar el número de teléfono y el ID de solicitud porque esos campos son necesarios para cancelar o comprobar el estado de cualquier solicitud de verificación. Y mientras que un usuario puede recordar su número de teléfono para cancelar o comprobar el estado de una verificación, no se puede esperar que recuerde una cadena de ID de solicitud de varios caracteres generada aleatoriamente. Por lo tanto, cuando la aplicación realice una solicitud de verificación, almacenaremos el identificador de solicitud requestId en SharedPreferences para recuperarlo más tarde en caso de que el usuario salga de la aplicación o se reinicie la actividad.

PhoneNumberConfirmActivity

Una vez que el usuario introduce el número de teléfono y el servidor responde con un 200la aplicación iniciará la PhoneNumberConfirmActivity. Ya he iniciado la en la rama getting-started rama

La estructura básica de la actividad ya está construida. Sólo queda utilizar el requestId y el número de teléfono para confirmar el código PIN o cancelar la verificación.

En primer lugar, vamos a empezar con la obtención del número de teléfono y requestId de SharedPreferences. Necesitaremos el número de teléfono e requestId para confirmar el código de verificación o cancelar el proceso de verificación.

@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...
}

A continuación, una vez que el usuario hace clic en el botón "Confirmar", la app tiene que enviar el código PIN y el requestId a la API del proxy. Podemos cablear el botón "Confirmar OnClickListener para iniciar esa solicitud.

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);
        }
  });
}

La solicitud de red para confirmar el código PIN es similar a la solicitud realizada anteriormente para iniciar el proceso de verificación. Si todo va bien el servidor responderá con un 200 OK y podremos informar al usuario de que está autenticado. Si el servidor responde con un 400 o 500 entonces podemos comprobar el errorText de la respuesta y alertar al usuario del problema ya sea con un brindis o mostrando el error en el archivo verifyTxt TextView que he creado.

Puede haber ocasiones en las que los usuarios quieran cancelar una solicitud de verificación. Esto puede deberse a que han introducido un número de teléfono incorrecto, quieren iniciar sesión con otra cuenta o simplemente no quieren verificarse en ese momento. La aplicación tiene que manejar este escenario para que podamos añadir un botón "Cancelar" a nuestra actividad y conectarla para enviar una solicitud de cancelación de red. Ya he añadido el botón "Cancelar" y he añadido un comando OnClickListener con un callback de cancelRequest()ahora sólo tenemos que añadir el código de red a ese 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);
        }
    });
}

Para terminar

Ahora hemos implementado todos los puntos finales necesarios para seguir un flujo de verificación. Si quieres ver el producto final de esta aplicación Android, el código fuente está en la rama finished en GitHub.

Próximos pasos

Si lo desea, puede implementar el resto de los puntos finales en la Verify API. Tenga en cuenta que esto requerirá que añada más puntos finales en el servidor proxy de la API. También puede añadir puntos finales adicionales para cubrir la Number Insight API. Esto también requerirá que añada más puntos finales en el servidor proxy de API. También hay una versión iOS de este post. Lea más de nuestro desarrollador Eric Giannini.

Riesgos/exención de responsabilidad

Es posible que desee inspeccionar el certificado SSL de las respuestas de su API proxy para asegurarse de que la aplicación cliente no está sujeta a un ataque MITM. Para más detalles visite la documentación para desarrolladores de Android.

Compartir:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Chris GuzmanAntiguos alumnos de Vonage

Chris es Developer Advocate en Nexmo, donde ayuda a los desarrolladores a utilizar su plataforma global de comunicaciones. Cuando no está en conferencias, se le puede encontrar vagando por el mundo.