
Partager:
Chris est Developer Advocate chez Nexmo où il aide les développeurs à utiliser leur plateforme de communication globale. Lorsqu'il n'est pas en conférence, vous pouvez le trouver en train de parcourir le monde.
Ajouter l'authentification à deux facteurs aux applications Android avec l'API Verify de Nexmo
Temps de lecture : 5 minutes
L'authentification à deux facteurs (2FA) ajoute une couche supplémentaire de sécurité pour les utilisateurs qui accèdent à des informations sensibles. Dans ce tutoriel, nous verrons comment mettre en œuvre l'authentification à deux facteurs pour le numéro de téléphone d'un utilisateur avec les points d'extrémité de l'API Verify de Nexmo.
Après avoir lu l'article de blog sur comment configurer un serveur pour utiliser Nexmo Verify vous êtes maintenant prêt à configurer une application Android pour travailler en réseau avec le serveur.
Pré-requis
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.
Cette application n'aura que deux dépendances : Retrofit pour passer des appels réseau et Moshi pour sérialiser et désérialiser JSON.
L'application devra faire plusieurs choses. Stocker un requestId afin qu'une demande de vérification puisse être annulée ou complétée. Elle doit également effectuer un appel réseau vers trois points d'extrémité :
Lancer une vérification
Vérifier un code de vérification
Annuler une demande de vérification
Pour commencer, j'ai mis en place une application de démonstration simple avec un écran de connexion demandant à l'utilisateur son adresse électronique, son mot de passe et son numéro de téléphone pour l'authentification à deux facteurs. l'authentification à deux facteurs (2FA). Clonez le repo suivant et naviguez jusqu'à la branche getting started :
git clone git@github.com:nexmo-community/verify-android-example.git
cd verify-android-example
git checkout getting-started Intégration avec le serveur proxy
Dans l'article de blog sur la configuration d'un serveur proxy pour l'API Verify, nous avons abordé trois points d'extrémité :
Faire une demande de vérification.
Vérifier un code de vérification.
Annuler une demande de vérification.
Nous allons donc devoir faire en sorte que notre application envoie des messages à ces trois points de terminaison. POSTà ces trois points d'extrémité. Pour ce faire, nous allons utiliser Retrofit et Moshi.
Faire des demandes de réseau
Le fichier build.gradle doit inclure les dépendances suivantes. Retrofit pour le réseau, Moshi pour l'analyse JSON, et OkHttp pour la journalisation.
//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'Nous pouvons commencer à les utiliser pour travailler en réseau avec notre API proxy.
Tout d'abord, nous allons mettre en place une interface pour les trois points d'extrémité que notre application doit atteindre :
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);
}Je ne reviendrai pas sur chacun des modèles pour les réponses, mais vous pouvez les consulter plus en détail ici. les voir plus en détail ici. Pour les besoins de ce tutoriel, j'ai fait correspondre la structure des modèles au JSON attendu par l Verify API attend.
Maintenant qu'il existe une interface de points d'extrémité, nous pouvons écrire une commande VerifyUtil qui instanciera Retrofit et effectuera les requêtes réseau :
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;
}
}J'ai créé VerifyUtil un singleton qui peut être appelé à partir de n'importe quelle activité.
Commençons par apporter quelques modifications à notre LoginActivity. L'activité signInBtn possède déjà un OnClickListener qui appelle start2FA() nous pouvons donc ajouter à cette méthode.
Activité de connexion
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);
}
});
}Lorsque quelqu'un clique sur le bouton "Se connecter", l'application envoie le numéro de téléphone à l'API proxy que nous avons mise en place dans le blog précédent. Cette application ignorera tout ce qui se trouve dans les écrans d'email ou de mot de passe puisque cette application est une preuve de concept.
En cas d'erreur dans la demande, l'API proxy enverra une réponse et nous la traiterons dans le cadre de l'API proxy. 400 et nous la traiterons dans le bloc else du bloc onResponse() callback. S'il y a une autre erreur, le serveur proxy répondra par un 500 et l'application peut gérer l'erreur dans le bloc onFailure() callback.
Si la demande aboutit, l'application enregistre le numéro de téléphone envoyé par l'utilisateur et le numéro de téléphone renvoyé par le serveur proxy. responseId que le serveur proxy a renvoyé, puis l'application démarre l'application PhoneNumberConfirmActivity afin que l'utilisateur puisse saisir son code. Il est important de stocker le numéro de téléphone et l'identifiant de la demande, car ces champs sont nécessaires pour annuler ou vérifier l'état d'une demande de vérification. Et si un utilisateur peut se souvenir de son numéro de téléphone pour annuler ou vérifier l'état d'une vérification, on ne peut pas s'attendre à ce qu'il se souvienne d'une chaîne d'identification de demande à plusieurs caractères, générée de manière aléatoire. Ainsi, lorsque l'application effectue une demande de vérification, nous stockons l'identifiant requestId dans SharedPreferences pour le récupérer plus tard au cas où l'utilisateur quitterait l'application ou que l'activité serait redémarrée.
Activité de confirmation du numéro de téléphone
Une fois que l'utilisateur a saisi le numéro de téléphone et que le serveur a répondu par un 200l'application lancera l'activité PhoneNumberConfirmActivity. J'ai déjà lancé l'activité sur la getting-started branche
La structure de base de l'activité est déjà construite. Il ne reste plus qu'à utiliser les touches requestId et le numéro de téléphone pour confirmer le code PIN ou annuler la vérification.
Tout d'abord, nous allons commencer par récupérer le numéro de téléphone et l'identifiant de la demande (requestId) à partir du fichier SharedPreferences. Nous aurons besoin du numéro de téléphone et de l'identifiant de la demande pour confirmer le code de vérification ou annuler le processus de vérification. requestId pour confirmer le code de vérification ou annuler le processus de vérification.
@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...
}Une fois que l'utilisateur a cliqué sur le bouton "Confirmer", l'application doit envoyer le code PIN et le numéro de série à l'API proxy. requestId à l'API proxy. Nous pouvons câbler le bouton "Confirm" en OnClickListener pour lancer cette requête.
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 demande du réseau pour confirmer le code PIN est similaire à la demande faite précédemment pour lancer le processus de vérification. Si tout se passe bien, le serveur répondra par un 200 OK et nous pouvons faire savoir à l'utilisateur qu'il est authentifié. Si le serveur répond par un 400 ou 500 nous pouvons vérifier le caractère errorText de la réponse et alerter l'utilisateur du problème, soit par un toast, soit en affichant l'erreur dans la fenêtre verifyTxt TextView que j'ai créée.
Il peut arriver que des utilisateurs souhaitent annuler une demande de vérification. Cela peut être dû au fait qu'ils ont saisi un mauvais numéro de téléphone, qu'ils veulent se connecter avec un autre compte ou qu'ils ne veulent tout simplement pas se vérifier eux-mêmes pour le moment. L'application doit gérer ce scénario afin que nous puissions ajouter un bouton "Annuler" à notre activité et la câbler pour envoyer une demande d'annulation au réseau. J'ai déjà ajouté le bouton "Annuler" et une fonction OnClickListener avec un rappel de cancelRequest()Il ne nous reste plus qu'à ajouter le code de mise en réseau à cette méthode.
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);
}
});
} Pour conclure
Nous avons maintenant mis en œuvre tous les points de terminaison nécessaires pour suivre un flux de vérification. Si vous souhaitez voir le produit fini de cette application Android, le code source se trouve sur la branche finished sur GitHub.
Prochaines étapes
Si vous le souhaitez, vous pouvez mettre en œuvre le reste des points de terminaison dans l'API Verify. Notez que cela vous obligera à ajouter d'autres points de terminaison dans le serveur proxy de l'API. Vous pouvez également ajouter des points de terminaison supplémentaires pour couvrir l'API Number Insight. Pour ce faire, vous devrez également ajouter d'autres points d'extrémité dans le serveur proxy de l'API. Il existe également une version iOS de cet article. Pour en savoir plus, lisez l'article d'Eric Giannini, notre défenseur des développeurs.
Risques/déni de responsabilité
Vous pouvez inspecter le certificat SSL des réponses de votre API proxy pour vous assurer que l'application cliente ne fait pas l'objet d'une attaque MITM. Pour plus de détails, consultez la documentation destinée aux développeurs d'Android.
