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

NexmoのVerify APIを使ってAndroidアプリに二要素認証を追加する

最終更新日 May 12, 2021

所要時間:1 分

二要素認証(2FA)は、機密情報にアクセスするユーザーのセキュリティをさらに強化します。このチュートリアルでは、NexmoのVerify APIエンドポイントを使用して、ユーザーの電話番号に二要素認証を実装する方法を説明します。

についてのブログ記事を読んだ後 NexmoVerifyを使用するためのサーバーの設定方法についてのブログ記事を読んだ後を使用するためにサーバーをセットアップする方法についてのブログ記事を読んだ後、サーバーとネットワークするためにAndroidアプリをセットアップする準備ができました。

前提条件

このアプリが依存するのは2つだけだ: レトロフィットMoshiです。

アプリはいくつかのことをする必要がある。を保存する。 requestIdを保存し、検証リクエストをキャンセルまたは完了できるようにする。また、3つのエンドポイントにネットワークコールを行う:

  • 検証を開始する

  • 検証コードを確認する

  • 検証リクエストのキャンセル

手始めに 簡単なデモアプリログイン画面では、ユーザーの電子メールアドレス、パスワード、電話番号の入力を求めます。 二要素認証(2FA)を設定します。以下のレポをクローンし、getting startedブランチに移動する:

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

プロキシサーバーとの統合

Verify API用のプロキシサーバーの設定方法についてのブログ投稿では、3つのエンドポイントを取り上げた:

  • 検証リクエストを行う。

  • 検証コードを確認する。

  • 検証リクエストをキャンセルする。

そこで、アプリがこれら3つのエンドポイントに POST送信する必要がある。そのためにRetrofitとMoshiを使おう。

ネットワークのリクエスト

build.gradleファイルには、以下の依存関係を含める必要がある。ネットワーク用のRetrofit、JSONパース用のMoshi、ロギング用のOkHttp。

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

プロキシAPIでネットワークを構築するために、それらを使い始めることができる。

まず、アプリがヒットする必要のある3つのエンドポイントのインターフェースを設定する:

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

回答のために各モデルを説明することはしない。 詳しくはこちらをご覧ください。.このチュートリアルの目的のために、私はモデルの構造を以下のJSONに合わせました。 Verify APIが期待するJSONに合わせました。.

エンドポイントのインターフェイスができたので、Retrofitをインスタンス化してネットワークリクエストを行う VerifyUtilを書くことができる:

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

私は VerifyUtilどのアクティビティからも呼び出せるシングルトンを作った。

まずは、LoginActivityを少し変更してみましょう。この signInBtnを呼び出すOnClickListenerを持っています。 start2FA()を呼び出しているので、そのメソッドに追加します。

ログインアクティビティ

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

誰かが "Sign In "ボタンをクリックすると、アプリは電話番号を以前のブログで設定したプロキシAPIに送信する。このアプリは概念実証なので、Eメールやパスワードの画面は何も無視する。

リクエストにエラーがあった場合、プロキシAPIは 400レスポンスを送信します。 elseブロックの onResponse()コールバックのブロックで処理します。その他のエラーがあれば、プロキシサーバーは 500で応答し、アプリは onFailure()コールバックでエラーを処理することができる。

リクエストが成功すると、アプリはユーザーが送信した電話番号とプロキシサーバーが返した電話番号を保存します。 responseIdプロキシサーバーが返したものを保存し、アプリは PhoneNumberConfirmActivityを開始し、ユーザーがコードを入力できるようにします。電話番号とリクエストIDを保存することが重要なのは、これらのフィールドが検証リクエストのキャンセルやステータスの確認に必要だからです。ユーザーは、検証のキャンセルやステータスを確認するために電話番号を覚えているかもしれないが、複数文字でランダムに生成されるリクエストID文字列を覚えていることは期待できない。そのため、アプリが認証リクエストを行う際に requestIdを保存します。 SharedPreferencesを保存し、ユーザーがアプリをバックグラウンドにしたり、アクティビティが再開されたりした場合に後で取得できるようにします。

電話番号確認アクティビティ

ユーザーが電話番号を入力し、サーバーから 200を返すと、アプリは PhoneNumberConfirmActivity を開始します。すでに アクティビティを getting-startedブランチ

アクティビティの基本構造はすでに出来上がっている。あとは requestIdと電話番号を使ってPINコードを確認するか、検証をキャンセルするかだけである。

まず、電話番号とrequestIdの取得から始めます。 SharedPreferences.電話番号と requestIdが必要です。

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

その後、ユーザーが「確認」ボタンをクリックすると、アプリはPINコードと requestIdをプロキシAPIに送信する必要がある。Confirm "ボタンの OnClickListenerを配線することができる。

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

PINコードを確認するためのネットワークリクエストは、先に検証プロセスを開始するために行われたリクエストと同様である。すべてがうまくいけば、サーバーは 200で応答し、ユーザーに認証されたことを知らせることができる。サーバからの応答が 400または 500で応答した場合は errorTextをチェックし、トーストを表示するか、エラーを verifyTxt TextView作成した

ユーザーが認証リクエストをキャンセルしたい場合があります。これは、間違った電話番号を入力したため、別のアカウントでログインしたいため、または単に現時点では自分自身を検証したくないためかもしれません。アプリはこのシナリオを処理する必要があるので、アクティビティに「キャンセル」ボタンを追加し、キャンセルネットワークリクエストを送信するように配線します。すでに "Cancel "ボタンを追加し、"Cancel "リクエストのコールバックを持つ OnClickListenerのコールバックを持つ cancelRequest()あとは、このメソッドにネットワーキング・コードを追加するだけです。

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

まとめ

これで、検証フローに従うために必要なすべてのエンドポイントを実装した。このAndroidアプリの完成品をご覧になりたい方は、ソースコードをご覧ください、 ソースコードは finishedブランチにあります。

次のステップ

必要であれば、Verify APIに残りのエンドポイントを実装することもできる。この場合、APIプロキシサーバーにさらにエンドポイントを追加する必要があることに注意してください。また、Number Insight API をカバーするエンドポイントを追加することもできます。この場合も、APIプロキシサーバーにさらにエンドポイントを追加する必要がある。この記事にはiOS版もあります。 詳しくは、開発者アドボケイトのEric Gianniniをご覧ください。

リスク/免責事項

クライアントアプリがMITM攻撃を受けていないことを確認するために、プロキシAPIからのレスポンスのSSL証明書を検査したい場合がある。詳細は Android開発者向けドキュメント.

シェア:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Chris Guzmanヴォネージの卒業生

クリスはNexmoのデベロッパー・アドボケイトとして、デベロッパーがグローバル・コミュニケーション・プラットフォームを使えるようにサポートしている。カンファレンスに出席していないときは、世界中を歩き回っている。