2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめての Meeting SDK - PKCE OAuth編 (Android)

Last updated at Posted at 2022-06-17

■Zoom - PKCE OAuth とは

 既存のOAuthでは、Zoomから提供される"Authorization Code"をサーバサイドで取り扱いする必要がありましたが、PKCE OAuthでは"Code Exchange"を採用することによりSDKクライアント側の実装でAPIリクエストに必要な認証トークンの取得まで実装いただけるようになっています。  また、Zoomユーザのメールアドレス、パスワードでZoomMeetingSDKクラインをご利用いただくことが可能になります。

(2022/06/17)

■実装の概要

ネイティブ(iOS, Android, Win, Mac)版のMeetingSDKにおいてZoomMeetingへ接続する際に、ユーザの識別にzakトークン(Zoom Access Token)を使用します。Zakトークンは、Rest_API経由で取得する必要があるため、Rest_APIを実行するための"access_token"(認証トークン)が必要となります。
ユーザに紐ずく"access_token"を取得する為には一旦ブラウザ経由でZoomのサインインページにアクセスしてメールアドレス、パスワードを入力しZoom側で認証後、指定のリダイレクトURL宛に"Authorization Code"を元に生成いただく必要があります。また、サーバサイドで処理は必要ありませんが、実在するリダイレクト先の指定は必要となります。事前にドメインの準備は必要となります。

■実装例

⑴ 初めにアプリを起動する準備をしていきます。
MarketplaceにサインインしてサンプルのSDKをダウンロードしておきます。
*詳しくは:はじめての Zoom Meeting SDK - 準備編
Android Studioから新しく"Empty Activity"でプロジェクトを作成し事前にダウンロードしておいたAndroid版のSDKをインポートしていきます。
*詳しくは:Import the Zoom SDK libraries

⑵ 識別認証で利用する対になる"code_challenge"と"code_verifier"を生成できるようにします。

    public final String createCodeVerifier() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] code = new byte[32];
        secureRandom.nextBytes(code);
        code_verifier = Base64.encodeToString(code, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
        return code_verifier;
    }

    public final String createCodeChallenge() throws NoSuchAlgorithmException, UnsupportedEncodingException {
        byte[] codeVerifierBytes = createCodeVerifier().getBytes("US-ASCII");
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(codeVerifierBytes);
        byte[] codeChallengeBytes = md.digest();
        code_challenge = Base64.encodeToString(codeChallengeBytes, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
        return code_challenge;
    }

⑶ Zoomのサインインページで "code_challenge"を付随して認証できるようにURIを生成します。

    public String createAuthorizeURI() throws UnsupportedEncodingException, NoSuchAlgorithmException {
        code_challenge = createCodeChallenge();
        Log.d(TAG, "code_challenge: " + code_challenge);
        String uriString = Marketplaceで生成したAdd_URL;
        Uri uri = Uri.parse(uriString)
                .buildUpon()
                .appendQueryParameter("code_challenge", code_challenge)
                .appendQueryParameter("code_challenge_method", "S256")
                .build();
        return uri.toString();
    }

⑷ WebViewを実装し先ほど生成したURIへアクセスできるようします。
ただし、必要なのはZoomへ認証した後の”Authorization Code”のみなのでhandlerを利用してリダイレクト先へアクセスする前にcodeだけを取得して次に繋げます。

        try {
            url = createAuthorizeURI();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        WebView webView = (WebView) findViewById(R.id.webView1);
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                final Uri uri = Uri.parse(url);
                return handleUri(uri);
            }
            private boolean handleUri(final Uri uri) {
                Log.i(TAG, "Uri = " + uri);
                final String host = uri.getHost();
                final String scheme = uri.getScheme();
                if (!host.equals(Marketplaceで指定したリダイレクトURLのドメイン)) {
                    return false; // continue loading page mainly for Zoom Authentication screen
                } else {
                    String code = uri.getQueryParameter("code");
                    Log.d(TAG, "code: " + code);
                    getAccessToken(code);
                    return true; // Stop loading once getting Authentication_Code
                }
            }
        });
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
        webView.loadUrl(url);

⑸ リダイレクトされた"Authorization Code"を元に"code_verifier"を付随して"access_token"を取得します。
*以下実際のHTTP処理は別HttpRequestClassにて実施しCallbackの様子になります。
*Callbackで取得したaccess_tokenとrefresh_tokenはその後利用できるようにSharedPreferencesに保存しておくと便利です。

public void getAccessToken(String code){
        Log.d(TAG, "getAccessToken");
        Log.d(TAG, "code_verifier: " + code_verifier);
        String Url = "https://zoom.us/oauth/token?code="+ code + "&grant_type=authorization_code&redirect_uri=" + <Marketplaceで指定したURLをencodeした値> + "&code_verifier=" + code_verifier;

        String clientid = Marketplaceで取得した Client ID;
        String clientsecret = Marketplaceで取得した Client Secret;
        final String baseCode = clientid + ":" + clientsecret;
        final String AuthCode = Base64.encodeToString(baseCode.getBytes(), Base64.URL_SAFE | Base64.NO_WRAP);

        String AuthType = "Basic";
        String RequestMethod = "POST";
        String ContentType = "application/x-www-form-urlencoded";

        HttpRequestClass httpRequest = new HttpRequestClass();
        httpRequest.setOnCallBack(new HttpRequestClass.CallBackTask(){

            @Override
            public void CallBack(String result) {
                super.CallBack(result);
                Log.d(TAG, "getAccessToken HttpRequestClass: " + result);
                if (isJSONValid(result)){
                    try {
                        JSONObject jsonObject = new JSONObject(result);
                        String access_token = jsonObject.getString("access_token");
                        String refresh_token = jsonObject.getString("refresh_token");
                        SharedPreferences.Editor editor = pref.edit();
                        editor.putString("access_token", access_token);
                        editor.putString("refresh_token", refresh_token);
                        editor.commit();
                        getUserInfo(access_token);
                    } catch (JSONException e) {
                        System.out.println("Error " + e.toString());
                    }
                }
            }

        });

        httpRequest.execute(Url, AuthType, AuthCode, RequestMethod, ContentType);
    }

⑹ 取得した"access_token"を利用してユーザ情報を取得します。
*以下実際のHTTP処理は別HttpRequestClassにて実施しCallbackの様子になります。
*Callbackで取得した"id"と"表示名"はその後利用できるようにSharedPreferencesに保存しておくと便利です。

public void getUserInfo(String access_token){
        Log.d(TAG, "getUserInfo");

        String Url = "https://api.zoom.us/v2/users/me";
        final String AuthCode = access_token;
        String AuthType = "Bearer";
        String RequestMethod = "GET";
        String ContentType = "application/json";

        HttpRequestClass httpRequest = new HttpRequestClass();
        httpRequest.setOnCallBack(new HttpRequestClass.CallBackTask(){

            @Override
            public void CallBack(String result) {
                super.CallBack(result);
                Log.d(TAG, "getUserInfo HttpRequestClass: " + result);
                if (isJSONValid(result)){
                    try {
                        JSONObject jsonObject = new JSONObject(result);
                        String id = jsonObject.getString("id");
                        String first_name = jsonObject.getString("first_name");
                        String last_name = jsonObject.getString("last_name");
                        String display_name = first_name + " " + last_name;
                        Log.d(TAG, "id: " + id);
                        SharedPreferences.Editor editor = pref.edit();
                        editor.putString("id", id);
                        editor.putString("display_name", display_name);
                        editor.commit();
                        finish();
                    } catch (JSONException e) {
                        System.out.println("Error " + e.toString());
                    }
                }
            }
        });
        httpRequest.execute(Url, AuthType, AuthCode, RequestMethod, ContentType);
    }

⑺ ここまで取得できたところで、最後に"access_token"を利用して"zak"トークンを取得しZoomMeetingを起動します。
注意:"access_token"が失効してしまっている場合には"refresh_token"を利用して別途取得し直す必要があります。

public void getZakToken(String ACCESS_TOKEN){
        Log.d(TAG, "getZakToken");

        String Url = "https://api.zoom.us/v2/users/me/zak";
        final String AuthCode = ACCESS_TOKEN;
        String AuthType = "Bearer";
        String RequestMethod = "GET";
        String ContentType = "application/json";

        HttpRequestClass httpRequest = new HttpRequestClass();
        httpRequest.setOnCallBack(new HttpRequestClass.CallBackTask(){

            @Override
            public void CallBack(String result) {
                super.CallBack(result);
                Log.d(TAG, "getZakToken HttpRequestClass: " + result);
                if (isJSONValid(result)){
                    try {
                        JSONObject jsonObject = new JSONObject(result);
                        String ZOOM_ACCESS_TOKEN = jsonObject.getString("token");
                        Log.d(TAG, "zak_token: " + ZOOM_ACCESS_TOKEN);
                        prepareZoomMeeting(ZOOM_ACCESS_TOKEN);
                    } catch (JSONException e) {
                        System.out.println("Error " + e.toString());
                    }
                }
            }
        });
        httpRequest.execute(Url, AuthType, AuthCode, RequestMethod, ContentType);
    }
        btn.setOnClickListener( v -> {
            Log.d(TAG, "button clicked: " + editTextNum.getText().toString());
            MEETING_ID = editTextNum.getText().toString();

            pref = this.getSharedPreferences("msdk_settings", Context.MODE_PRIVATE);
            USER_ID = pref.getString("id", null);
            DISPLAY_NAME = pref.getString("display_name", null);
            final String access_token = pref.getString("access_token", null);
            ZoomSDK zoomSDK = ZoomSDK.getInstance();
            if(zoomSDK.isInitialized()) {
                registerMeetingServiceListener();
                getZakToken(access_token);
            }else{
                Log.e(TAG, "zoomSDK is not initialized");
            }
        });

■サンプル

補足
marketplace.zoom.usへサインインして「Build App」よりSDKappを登録した上では、別途Android版SDKをダウンロードする必要があります。
MarketplaceからダウンロードしたSDKのzipファイル内からそれぞれ「commonlib」、「mobilertc」に含まれるファイルをサンプル内のフォルダへコピーしてからAndroidStudioで開いでください。
また、Contrants.classにinterfeceを用意していますので、Marketaplceで採取、指定しただいたクレデンシャル、リダイレクトURLを入力してからBuildするようにしてください。
Marketplaceで指定するリダイレクトURL先での処理の必要はありませんが、何らか応答できるページを持っている必要があります。

pxce_sample_screenshots.gif

■その他参考資料

PKCE OAuth tutorial
 https://marketplace.zoom.us/docs/sdk/native-sdks/android/build-an-app/pkce

OAuth with Zoom
 https://marketplace.zoom.us/docs/guides/auth/oauth

はじめての Zoom Meeting SDK - 準備編
 https://qiita.com/yosuke-sawamura/items/de69e73e47335cd61d68

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?