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

Hinzufügen von Zwei-Faktor-Authentifizierung zu Android-Apps mit Nexmo's Verify API

Zuletzt aktualisiert am May 12, 2021

Lesedauer: 4 Minuten

Zwei-Faktor-Authentifizierung (2FA) bietet eine zusätzliche Sicherheitsebene für Benutzer, die auf sensible Informationen zugreifen. In diesem Tutorial wird beschrieben, wie die Zwei-Faktor-Authentifizierung für die Telefonnummer eines Benutzers mit den Verify-API-Endpunkten von Nexmo implementiert werden kann.

Nach dem Lesen des Blogbeitrags über wie man einen Server zur Verwendung von Nexmo Verify einrichtet gelesen haben, sind Sie nun bereit, eine Android-App für die Vernetzung mit dem Server einzurichten.

Voraussetzungen

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

Diese Anwendung wird nur zwei Abhängigkeiten haben: Nachrüstung für Netzanrufe und Moshi für die Serialisierung und Deserialisierung von JSON.

Die App muss ein paar Dinge tun. Speichern Sie eine requestId damit eine Verifizierungsanfrage abgebrochen oder abgeschlossen werden kann. Außerdem muss sie einen Netzanruf an drei Endpunkte tätigen:

  • Starten Sie eine Überprüfung

  • Überprüfen eines Verifizierungscodes

  • Abbrechen einer Überprüfungsanfrage

Für den Anfang habe ich eine einfache Demo-App mit einem Anmeldebildschirm, der nach der E-Mail-Adresse, dem Passwort und der Telefonnummer des Benutzers für die Zwei-Faktoren-Authentifizierung (2FA). Klonen Sie das folgende Repo und navigieren Sie zum Getting Started-Zweig:

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

Integration mit Proxy Server

In dem Blogbeitrag über die Einrichtung eines Proxy-Servers für die Verify-API haben wir drei Endpunkte behandelt:

  • Stellen Sie einen Überprüfungsantrag.

  • Überprüfen Sie einen Verifizierungscode.

  • Stornieren Sie eine Überprüfungsanfrage.

Wir müssen also unsere App an diese drei Endpunkte senden POSTan diese drei Endpunkte senden. Hierfür werden wir Retrofit und Moshi verwenden.

Netzwerkanfragen stellen

Die Datei build.gradle sollte die folgenden Abhängigkeiten enthalten. Retrofit für die Vernetzung, Moshi für das JSON-Parsing und OkHttp für die Protokollierung.

//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'

Wir können sie für die Vernetzung mit unserer Proxy-API verwenden.

Zunächst richten wir eine Schnittstelle für die drei Endpunkte ein, auf die unsere Anwendung zugreifen muss:

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

Ich werde hier nicht auf jedes einzelne Modell eingehen, aber Sie können sich Sie können sie hier im Detail betrachten. Für die Zwecke dieses Tutorials habe ich die Struktur der Modelle an das JSON angepasst, das Verify API erwartet.

Da es nun eine Schnittstelle für Endpunkte gibt, können wir ein VerifyUtil schreiben, das Retrofit instanziiert und die Netzwerkanfragen stellt:

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

Ich habe ein VerifyUtil ein Singleton, das von jeder Activity aufgerufen werden kann.

Beginnen wir damit, einige Änderungen an unserer LoginActivity vorzunehmen. Die signInBtn hat bereits einen OnClickListener, der die Methode start2FA() aufruft, so dass wir diese Methode ergänzen können.

Login-Aktivität

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

Immer wenn jemand auf die Schaltfläche "Anmelden" klickt, sendet die App die Telefonnummer an die Proxy-API, die wir in einem früheren Blog eingerichtet haben. Diese App ignoriert alles, was in den E-Mail- oder Passwort-Bildschirmen steht, da es sich bei dieser App um einen Proof of Concept handelt.

Wenn bei der Anfrage ein Fehler auftritt, sendet die Proxy-API eine 400 Antwort und wir behandeln sie im else Block des onResponse() Rückrufs. Wenn ein anderer Fehler auftritt, antwortet der Proxyserver mit einer 500 und die Anwendung kann den Fehler in dem onFailure() Rückruf behandeln.

Wenn die Anfrage erfolgreich ist, speichert die App die vom Benutzer gesendete Telefonnummer und den responseId die der Proxy-Server zurückgegeben hat, dann startet die App die PhoneNumberConfirmActivity damit der Benutzer seinen Code eingeben kann. Es ist wichtig, die Telefonnummer und die Anfrage-ID zu speichern, da diese Felder benötigt werden, um eine Verifizierungsanfrage zu stornieren oder ihren Status zu überprüfen. Ein Benutzer kann sich zwar an seine Telefonnummer erinnern, um eine Verifizierungsanfrage abzubrechen oder ihren Status zu überprüfen, aber man kann nicht erwarten, dass er sich an eine mehrstellige, zufällig generierte Anfrage-ID erinnert. Wenn die Anwendung also eine Verifizierungsanfrage stellt, speichern wir die requestId in SharedPreferences um sie später abzurufen, falls der Benutzer die App zurücksetzt oder die Aktivität neu gestartet wird.

PhoneNumberConfirmActivity

Sobald ein Benutzer die Telefonnummer eingibt und der Server mit einer 200antwortet, wird die App die PhoneNumberConfirmActivity starten. Ich habe bereits die Aktivität auf dem getting-started Zweig

Die Grundstruktur der Aktivität ist bereits erstellt. Jetzt müssen Sie nur noch den requestId und die Telefonnummer, um den PIN-Code zu bestätigen oder die Überprüfung abzubrechen.

Zunächst holen wir uns die Telefonnummer und requestId aus SharedPreferences. Wir benötigen die Telefonnummer und requestId um den Verifizierungscode zu bestätigen oder den Verifizierungsprozess abzubrechen.

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

Sobald der Benutzer auf die Schaltfläche "Bestätigen" klickt, muss die App den PIN-Code und die requestId an die Proxy-API senden. Wir können die "Bestätigen"-Schaltfläche verdrahten OnClickListener um diese Anfrage zu starten.

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

Die Netzwerkanfrage zur Bestätigung des PIN-Codes ähnelt der Anfrage, die zuvor zum Start des Verifizierungsprozesses gestellt wurde. Wenn alles gut geht, antwortet der Server mit einem 200 OK und wir können dem Benutzer mitteilen, dass er authentifiziert ist. Antwortet der Server mit einem 400 oder 500 antwortet, können wir die errorText der Antwort überprüfen und den Benutzer entweder mit einem Toast oder durch Anzeige des Fehlers in der verifyTxt TextView die ich erstellt habe.

Es kann vorkommen, dass Benutzer eine Verifizierungsanfrage abbrechen möchten. Dies kann der Fall sein, wenn sie die falsche Telefonnummer eingegeben haben, sich mit einem anderen Konto anmelden möchten oder sich einfach nicht verifizieren wollen. Die App muss mit diesem Szenario umgehen können, damit wir eine Schaltfläche "Abbrechen" zu unserer Aktivität hinzufügen und sie so verdrahten können, dass sie eine Stornierungsnetzwerkanforderung sendet. Ich habe die Schaltfläche "Abbrechen" bereits hinzugefügt und eine OnClickListener mit einem Rückruf von cancelRequest()hinzugefügt, jetzt können wir nur noch den Netzwerkcode zu dieser Methode hinzufügen.

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

Einpacken

Jetzt haben wir alle Endpunkte implementiert, die notwendig sind, um einen Verifizierungsfluss zu verfolgen. Wenn Sie das fertige Produkt dieser Android-App sehen möchten, der Quellcode befindet sich auf dem finished Zweig auf GitHub.

Nächste Schritte

Wenn Sie möchten, können Sie den Rest der Endpunkte in der Verify-API implementieren. Beachten Sie, dass Sie dazu weitere Endpunkte im API-Proxy-Server hinzufügen müssen. Sie können auch zusätzliche Endpunkte hinzufügen, um die Number Insights API abzudecken. Dazu müssen Sie ebenfalls weitere Endpunkte im API-Proxyserver hinzufügen. Es gibt auch eine iOS-Version dieses Beitrags. Lesen Sie mehr von unserem Entwicklerberater Eric Giannini.

Risiken/Haftungsausschluss

Sie können das SSL-Zertifikat der Antworten von Ihrer Proxy-API überprüfen, um sicherzustellen, dass die Client-Anwendung nicht einem MITM-Angriff ausgesetzt ist. Für weitere Details, besuchen Sie die Android-Entwicklerdokumentation.

Share:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Chris GuzmanVonage Ehemalige

Chris ist Developer Advocate bei Nexmo, wo er Entwicklern hilft, ihre globale Kommunikationsplattform zu nutzen. Wenn er nicht gerade auf Konferenzen ist, kann man ihn in der Welt herumreisen sehen.