Questions? Feedback? powered by Olark live chat software
Ignorar Navegação

Logging in with Google, Microsoft and Facebook SDKs to Azure Mobile Services

Publicado em 27 outubro, 2014

SR. Software Engineer, Azure Mobile Services
One of the values that Azure Mobile Services provides is an easy way to implement authentication for mobile applications, via a very simple API – call a login function (or equivalent) on the client object in any of the supported platforms, and your user gets presented with a simple web-based interface that allows them to log in to your mobile service. This is what we call a server-side authentication flow, where the service guides the client to the provider (via redirections in the web page) and then back to itself. Using a server-side authentication is a good way to start an application, but if the authentication provider which you want to use has some native SDK that supports login, then your user would have a better experience if your app used it. For example, if your user is in an android device, it’s very likely that they have a Google account associated with their device, so if they can use that account without having to re-enter their credentials, that makes for a better user experience. Same thing with a Facebook account in an Android, iOS or Windows Phone device, or a Microsoft account on a Windows or Windows Phone. This scenario is what we call a client-side authentication flow, where the client application talks directly to the provider (via its native SDK) and then just exchanges some token from the provider with the mobile service to authenticate with the service itself. We’ve had the client-side authentication flow working for Facebook and Microsoft authentication in the node.js backend for some time, but not in the .NET runtime. We also didn’t support Google SDK authentication in any of the runtimes – until now. We just released support for Google authentication in the node.js backend, in addition to supporting authentication via data from SDK from all those three social providers in the .NET SDK. This has been a long-requested feature that we’re glad to see live. In this post I’ll walk through how to use this new feature.

Microsoft Accounts

Logging in to Windows Phone / Windows Store apps from the Live SDK has been an option to node.js backend since the first release of mobile services (in node.js). Now that it’s enabled in the .NET backend, the steps to enable it are exactly the same as in node: register the app for authentication and copy the Windows Store dashboard information to the mobile service’s identity tab, add Live SDK to the application; login using the Live SDK and later send the authentication token received from the SDK to the mobile service. All those steps are listed in the tutorial at https://azure.microsoft.com/en-us/documentation/articles/mobile-services-windows-store-dotnet-single-sign-on/ and you can follow them to add the Live SDK login to your application which uses the .NET backend.

Facebook Login

Similarly to the case with Microsoft account, logging in with tokens from the Facebook SDK has been an option in the node.js backend for a long time. The .NET backend now also supports this client flow. Also, like in the previous section, there’s absolutely no difference in the client code (or even in the code in the server that you have to write), so all the tutorials for node apply to the .NET backend as well. You can find more information about adding login via Facebook SDK to your apps in the following blog posts: iOS Apps and Android Apps.

Google Login

The client authentication flow for Google accounts is a new feature for both the node.js and the .NET backend, and it works the same for both. Let’s go through the scenario of adding a login operation to an Android app.

Register the app for authentication

Before we start authenticating the native Android app with the Google SDK, we need to “prepare” the backend mobile service by connecting it to a project in the Google Developers Console. To do that, follow the first section of the Add authentication to your Mobile Service app tutorial (following the option to Register your apps for Google login with Mobile Services). Once that’s done, we can move on to the native part.

Integrate Google+ into your Android app

To add the Google sign-in to your application you need to add the Google Play Services library to your application. The getting started page in the Google documentation has all the steps required to do so. Notice that in first step, where it tells you to create a new project in the Google Developers Console, instead of doing that you’ll be using the same project which you created in the previous step. In your project you’ll already have a client ID for web application, and you’ll be adding a new OAuth client ID for an Android Installed application.

Adding Sign-in for your Android app

Once Google Play is properly set up for your project, we can now start using it to log in, first to Google itself, then to the mobile services. Before using the Google Play Services, we should always check whether it exists (it can save you a lot of time debugging in case your emulator isn’t properly setup, as it happened to me before).
    btn.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            int isAvailableResult = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity);
            if (isAvailableResult == ConnectionResult.SUCCESS) {
                Log.d("msg", "Result for isGooglePlayServicesAvailable: SUCCESS");
                pickUserAccount();
            } else {
                Log.e("error", "Google play services is not available: " + isAvailableResult);                
            }
        }
    });
Once we’ve established that the service is running in the device (real or virtual), then we can choose the account which is associated with the device that the user wants to use. For that we’ll use the AccountPicker class, which will show a UI for the user to select their account, or if the user has only one account, then it will automatically choose it without needing for user intervention (a better experience).
    static final int REQUEST_CODE_PICK_ACCOUNT = 1000;
    private void pickUserAccount() {
        String[] accountTypes = new String[] { "com.google" };
        Intent intent = AccountPicker.newChooseAccountIntent(null, null, accountTypes, false, null, null, null, null);
        startActivityForResult(intent, REQUEST_CODE_PICK_ACCOUNT);
    }
Since the picker UI is presented as a separate activity, we need to override the onActivityResult method to get the result. In the result we can get the name of the account that the user wants to use, and once that’s done, we can request the token that is necessary to log in to the mobile service.
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE_PICK_ACCOUNT) {
            if (resultCode == RESULT_OK) {
                String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                Log.d("msg", "Account name: " + accountName);
                getTokenAndLogin(accountName);
            } else if (resultCode == RESULT_CANCELED) {
                Log.d("msg", "Activity cancelled by user");
            }
        }
    }
The token which we need from Google to login to the service is an ID Token. An Android ID token is used to authenticate the user to a web application so that Android apps can talk to the remote service without requiring the user to log in again. This is a form of cross-client authentication, in which the user authenticates with the native app defined in the developers console, and then presents the token to the remote app (the mobile service), which in turns validates with Google that the token comes from the same project. Remember that we created two OAuth client IDs associated with the same project? That’s how the mobile service and Google can communicate to ensure that the user is authenticated for that application only. To retrieve the id token the device needs to be online (another good practice) and then go to a background thread to retrieve the token – for which we’ll be using an AsyncTask.
    static final String GOOGLE_SCOPE_TAKE2 = "audience:server:client_id:";
    static final String CLIENT_ID_WEB_APPS = "0000000000000-0aaaaaaa00aa00aa0aaa0a0aaaaaaa0a.apps.googleusercontent.com";
    static final String GOOGLE_ID_TOKEN_SCOPE = GOOGLE_SCOPE_TAKE2 + CLIENT_ID_WEB_APPS;

    private void getTokenAndLogin(String accountName) {
        if (mAccountName == null) {
            pickUserAccount();
        } else {
            if (isDeviceOnline()) {
                new GetTokenAndLoginTask(this, GOOGLE_ID_TOKEN_SCOPE, accountName).execute((Void)null);
            } else {
                Toast.makeText(this, R.string.not_online, Toast.LENGTH_LONG).show();
            }
        }
    }

    private boolean isDeviceOnline() {
        ConnectivityManager mgr = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = mgr.getActiveNetworkInfo();
        return netInfo != null && netInfo.isConnected();
    }
When we validate that the device is online, we can use the GoogleAuthUtil.getToken method that takes the account we want to use and the scope of the request. For cross-client authentication, the scope is defined as the prefix “audience:server:client_id:” followed by the client id for web application (which was created in the first step for this section) – do not use the client id for Android application in this case. If the user has yet to authorize the application to use their data, then a UserRecoverableAuthException is thrown, and we can deal with it as we’ll show later. If the user has authorized the app use their information, then a token will be returned, and we can take that token and send to the mobile service’s login call, by wrapping it in an object with a property called “id_token”. Once that call is done, we’re logged in to the mobile service and can access protected resources.
    class GetTokenAndLoginTask extends AsyncTask<Void, Void, Void> {

        MainActivity mActivity;
        String mScope;
        String mEmail;

        public GetTokenAndLoginTask(MainActivity activity, String scope, String email) {
            this.mActivity = activity;
            this.mScope = scope;
            this.mEmail = email;
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                final String token = fetchIdToken();
                if (token != null) {
                    loginToMobileService(token);
                }
            } catch (IOException e) {
                Log.e("error", "Exception: " + e);
            }

            return null;
        }

        protected void loginToMobileService(final String idToken) {
            runOnUiThread(new Runnable(){

                @Override
                public void run() {
                    mActivity.updateTextView("Token: " + idToken);
                    JsonObject loginBody = new JsonObject();
                    loginBody.addProperty("id_token", idToken);
                    mClient.login(MobileServiceAuthenticationProvider.Google, loginBody, new UserAuthenticationCallback() {

                        @Override
                        public void onCompleted(MobileServiceUser user, Exception error,
                                ServiceFilterResponse response) {
                            if (error != null) {
                                Log.e("error", "Login error: " + error);
                            } else {
                                Log.d("msg", "Logged in to the mobile service as " + user.getUserId());
                            }
                        }
                    });
                }
            });
        }

        protected String fetchIdToken() throws IOException {
            try {
                return GoogleAuthUtil.getToken(mActivity, mEmail, mScope);
            } catch (UserRecoverableAuthException urae) {
                mActivity.handleException(urae);
            } catch (GoogleAuthException gae) {
                Log.e("error", "Unrecoverable exception: " + gae);
            }
            return null;
        }
    }
The last piece of the puzzle is to handle the case where the getToken method returned a recoverable exception. In this case, we can handle the exception by starting the activity given to us by the exception, which will show the dialog prompting the user to authorize the app to be used. We need to then update the onActivityResult implementation so that when the app returns from that authorization screen, it can make the call to get the token again (and, if the user has authorized the app, it will succeed).
    static final int REQUEST_CODE_PICK_ACCOUNT = 1000;
    static final int REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR = 1001;

    public void handleException(final Exception e) {
        // Because this call comes from the AsyncTask, we must ensure that the following
        // code instead executes on the UI thread.
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (e instanceof GooglePlayServicesAvailabilityException) {
                    // The Google Play services APK is old, disabled, or not present.
                    // Show a dialog created by Google Play services that allows
                    // the user to update the APK
                    int statusCode = ((GooglePlayServicesAvailabilityException)e)
                            .getConnectionStatusCode();
                    Dialog dialog = GooglePlayServicesUtil.getErrorDialog(statusCode,
                            MainActivity.this,
                            REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
                    dialog.show();
                } else if (e instanceof UserRecoverableAuthException) {
                    // Unable to authenticate, such as when the user has not yet granted
                    // the app access to the account, but the user can fix this.
                    // Forward the user to an activity in Google Play services.
                    Intent intent = ((UserRecoverableAuthException)e).getIntent();
                    startActivityForResult(intent,
                            REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
                }
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE_PICK_ACCOUNT) {
            if (resultCode == RESULT_OK) {
                String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                Log.d("msg", "Account name: " + accountName);
                getTokenAndLogin(accountName);
            } else if (resultCode == RESULT_CANCELED) {
                Log.d("msg", "Activity cancelled by user");
            }
        } else if (requestCode == REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR && resultCode == RESULT_OK) {
            getTokenAndLogin();
        }
    }
And with that we’ve shown how you can add native Google authentication to your mobile app and use it to log in to the mobile service. As I mentioned before, this works the same way for both the node.js and the .NET backends.

Wrapping up

It’s always good when we’re able to release a feature that have been requested for a while and this week we did that. Hopefully this will help Azure Mobile Services add more value to your applications, by giving your users a better user login experience. As usual, please send us feedback either as comments in this post, via twitter @AzureMobile or in our MSDN forums.