Add authentication to your Android app

Note

Visual Studio App Center is investing in new and integrated services central to mobile app development. Developers can use Build, Test and Distribute services to set up Continuous Integration and Delivery pipeline. Once the app is deployed, developers can monitor the status and usage of their app using the Analytics and Diagnostics services, and engage with users using the Push service. Developers can also leverage Auth to authenticate their users and Data service to persist and sync app data in the cloud. Check out App Center today.

Summary

In this tutorial, you add authentication to the todolist quickstart project on Android by using a supported identity provider. This tutorial is based on the Get started with Mobile Apps tutorial, which you must complete first.

Register your app for authentication and configure Azure App Service

First, you need to register your app at an identity provider's site, and then you will set the provider-generated credentials in the Mobile Apps back end.

  1. Configure your preferred identity provider by following the provider-specific instructions:

  2. Repeat the previous steps for each provider you want to support in your app.

Add your app to the Allowed External Redirect URLs

Secure authentication requires that you define a new URL scheme for your app. This allows the authentication system to redirect back to your app once the authentication process is complete. In this tutorial, we use the URL scheme appname throughout. However, you can use any URL scheme you choose. It should be unique to your mobile application. To enable the redirection on the server side:

  1. In the Azure portal, select your App Service.

  2. Click the Authentication / Authorization menu option.

  3. In the Allowed External Redirect URLs, enter appname://easyauth.callback. The appname in this string is the URL Scheme for your mobile application. It should follow normal URL specification for a protocol (use letters and numbers only, and start with a letter). You should make a note of the string that you choose as you will need to adjust your mobile application code with the URL Scheme in several places.

  4. Click OK.

  5. Click Save.

Restrict permissions to authenticated users

By default, APIs in a Mobile Apps back end can be invoked anonymously. Next, you need to restrict access to only authenticated clients.

  • Node.js back end (via the Azure portal) :

    In your Mobile Apps settings, click Easy Tables and select your table. Click Change permissions, select Authenticated access only for all permissions, and then click Save.

  • .NET back end (C#):

    In the server project, navigate to Controllers > TodoItemController.cs. Add the [Authorize] attribute to the TodoItemController class, as follows. To restrict access only to specific methods, you can also apply this attribute just to those methods instead of the class. Republish the server project.

      [Authorize]
      public class TodoItemController : TableController<TodoItem>
    
  • Node.js backend (via Node.js code) :

    To require authentication for table access, add the following line to the Node.js server script:

      table.access = 'authenticated';
    

    For more details, see How to: Require authentication for access to tables. To learn how to download the quickstart code project from your site, see How to: Download the Node.js backend quickstart code project using Git.

  • In Android Studio, open the project you completed with the tutorial Get started with Mobile Apps. From the Run menu, click Run app, and verify that an unhandled exception with a status code of 401 (Unauthorized) is raised after the app starts.

    This exception happens because the app attempts to access the back end as an unauthenticated user, but the TodoItem table now requires authentication.

Next, you update the app to authenticate users before requesting resources from the Mobile Apps back end.

Add authentication to the app

  1. Open the project in Android Studio.

  2. In Project Explorer in Android Studio, open the ToDoActivity.java file and add the following import statements:

    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.content.SharedPreferences.Editor;
    
    import com.microsoft.windowsazure.mobileservices.authentication.MobileServiceAuthenticationProvider;
    import com.microsoft.windowsazure.mobileservices.authentication.MobileServiceUser;
    
  3. Add the following method to the ToDoActivity class:

    // You can choose any unique number here to differentiate auth providers from each other. Note this is the same code at login() and onActivityResult().
    public static final int GOOGLE_LOGIN_REQUEST_CODE = 1;
    
    private void authenticate() {
        // Sign in using the Google provider.
        mClient.login(MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // When request completes
        if (resultCode == RESULT_OK) {
            // Check the request code matches the one we send in the login request
            if (requestCode == GOOGLE_LOGIN_REQUEST_CODE) {
                MobileServiceActivityResult result = mClient.onActivityResult(data);
                if (result.isLoggedIn()) {
                    // sign-in succeeded
                    createAndShowDialog(String.format("You are now signed in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
                    createTable();
                } else {
                    // sign-in failed, check the error message
                    String errorMessage = result.getErrorMessage();
                    createAndShowDialog(errorMessage, "Error");
                }
            }
        }
    }
    

    This code creates a method to handle the Google authentication process. A dialog displays the ID of the authenticated user. You can only proceed on a successful authentication.

    Note

    If you are using an identity provider other than Google, change the value passed to the login method to one of the following values: MicrosoftAccount, Facebook, Twitter, or windowsazureactivedirectory.

  4. In the onCreate method, add the following line of code after the code that instantiates the MobileServiceClient object.

    authenticate();
    

    This call starts the authentication process.

  5. Move the remaining code after authenticate(); in the onCreate method to a new createTable method:

    private void createTable() {
    
        // Get the table instance to use.
        mToDoTable = mClient.getTable(ToDoItem.class);
    
        mTextNewToDo = (EditText) findViewById(R.id.textNewToDo);
    
        // Create an adapter to bind the items with the view.
        mAdapter = new ToDoItemAdapter(this, R.layout.row_list_to_do);
        ListView listViewToDo = (ListView) findViewById(R.id.listViewToDo);
        listViewToDo.setAdapter(mAdapter);
    
        // Load the items from Azure.
        refreshItemsFromTable();
    }
    
  6. To ensure redirection works as expected, add the following snippet of RedirectUrlActivity to AndroidManifest.xml:

    <activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="{url_scheme_of_your_app}"
                android:host="easyauth.callback"/>
        </intent-filter>
    </activity>
    
  7. Add redirectUriScheme to build.gradle of your Android application.

    android {
        buildTypes {
            release {
                // ...
                manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
            }
            debug {
                // ...
                manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
            }
        }
    }
    
  8. Add com.android.support:customtabs:23.0.1 to the dependencies in your build.gradle:

    dependencies {
        // ...
        compile 'com.android.support:customtabs:23.0.1'
    }
    
  9. From the Run menu, click Run app to start the app and sign in with your chosen identity provider.

Warning

The URL Scheme mentioned is case-sensitive. Ensure that all occurrences of {url_scheme_of_you_app} use the same case.

When you are successfully signed in, the app should run without errors, and you should be able to query the back-end service and make updates to data.

Cache authentication tokens on the client

The previous example showed a standard sign-in, which requires the client to contact both the identity provider and the back-end Azure service every time the app starts. This method is inefficient, and you can have usage-related issues if many customers try to start your app simultaneously. A better approach is to cache the authorization token returned by the Azure service, and try to use this first before using a provider-based sign-in.

Note

You can cache the token issued by the back-end Azure service regardless of whether you are using client-managed or service-managed authentication. This tutorial uses service-managed authentication.

  1. Open the ToDoActivity.java file and add the following import statements:

    import android.content.Context;
    import android.content.SharedPreferences;
    import android.content.SharedPreferences.Editor;
    
  2. Add the following members to the ToDoActivity class.

    public static final String SHAREDPREFFILE = "temp";
    public static final String USERIDPREF = "uid";
    public static final String TOKENPREF = "tkn";
    
  3. In the ToDoActivity.java file, add the following definition for the cacheUserToken method.

    private void cacheUserToken(MobileServiceUser user)
    {
        SharedPreferences prefs = getSharedPreferences(SHAREDPREFFILE, Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString(USERIDPREF, user.getUserId());
        editor.putString(TOKENPREF, user.getAuthenticationToken());
        editor.commit();
    }
    

    This method stores the user ID and token in a preference file that is marked private. This should protect access to the cache so that other apps on the device do not have access to the token. The preference is sandboxed for the app. However, if someone gains access to the device, it is possible that they may gain access to the token cache through other means.

    Note

    You can further protect the token with encryption, if token access to your data is considered highly sensitive and someone may gain access to the device. A completely secure solution is beyond the scope of this tutorial, however, and depends on your security requirements.

  4. In the ToDoActivity.java file, add the following definition for the loadUserTokenCache method.

    private boolean loadUserTokenCache(MobileServiceClient client)
    {
        SharedPreferences prefs = getSharedPreferences(SHAREDPREFFILE, Context.MODE_PRIVATE);
        String userId = prefs.getString(USERIDPREF, null);
        if (userId == null)
            return false;
        String token = prefs.getString(TOKENPREF, null);
        if (token == null)
            return false;
    
        MobileServiceUser user = new MobileServiceUser(userId);
        user.setAuthenticationToken(token);
        client.setCurrentUser(user);
    
        return true;
    }
    
  5. In the ToDoActivity.java file, replace the authenticate and onActivityResult methods with the following ones, which uses a token cache. Change the login provider if you want to use an account other than Google.

    private void authenticate() {
        // We first try to load a token cache if one exists.
        if (loadUserTokenCache(mClient))
        {
            createTable();
        }
        // If we failed to load a token cache, sign in and create a token cache
        else
        {
            // Sign in using the Google provider.
            mClient.login(MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE);
        }
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // When request completes
        if (resultCode == RESULT_OK) {
            // Check the request code matches the one we send in the sign-in request
            if (requestCode == GOOGLE_LOGIN_REQUEST_CODE) {
                MobileServiceActivityResult result = mClient.onActivityResult(data);
                if (result.isLoggedIn()) {
                    // sign-in succeeded
                    createAndShowDialog(String.format("You are now signed in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
                    cacheUserToken(mClient.getCurrentUser());
                    createTable();
                } else {
                    // sign-in failed, check the error message
                    String errorMessage = result.getErrorMessage();
                    createAndShowDialog(errorMessage, "Error");
                }
            }
        }
    }
    
  6. Build the app and test authentication using a valid account. Run it at least twice. During the first run, you should receive a prompt to sign in and create the token cache. After that, each run attempts to load the token cache for authentication. You should not be required to sign in.

Next steps

Now that you completed this basic authentication tutorial, consider continuing on to one of the following tutorials:

  • Add push notifications to your Android app. Learn how to configure your Mobile Apps back end to use Azure notification hubs to send push notifications.
  • Enable offline sync for your Android app. Learn how to add offline support to your app by using a Mobile Apps back end. With offline sync, users can interact with a mobile app—viewing, adding, or modifying data—even when there is no network connection.