close icon
Android

Working with Auth0 User and App Metadata in Android Apps

Learn how to use user and app metadata in Auth0 user profiles to store additional information about your users.

August 26, 2022

After learning how to add authentication to your Android apps, you might want to store additional information about your users. You can do this by accessing the metadata in Auth0’s user profiles, which you’ll learn how to do in this tutorial.

If you’re not familiar with integrating Auth0 authentication with Android apps, we strongly recommend that you first go through the exercises in Get Started with Android Authentication Using Kotlin.

Look for the 🛠 emoji if you’d like to skim through the content while focusing on the build and execution steps. Harness the power of control-F/command-F to build the app in this tutorial quickly!

User Metadata and App Metadata

Auth0’s user profiles store basic information about each user: a unique identifier, the names they use, their contact details, and other identifying information such as their picture. These are useful to know during the authentication process, but you may often need other information as the user logs in, such as:

  • The user’s status: Is the user using the app on a trial basis, or are they a paying customer? Does the user have access to the app’s basic features, or did they purchase the deluxe package with all the features?
  • User preferences, such as a preferred color scheme, light mode vs. dark mode, font size, and so on.
  • Flags that track actions that the user has taken. Has the user accepted the app’s terms and conditions? Have they completed the tutorial?
  • Other information about the user’s account: Is it time to show the user an alert, announcement, or notice? Is there an available upgrade for the app?

Auth0’s user profiles can store this kind of information as metadata, which your app can access once the user has logged in. This is information that your app could keep in its storage system, but Auth0’s user profile metadata has the advantage of being available immediately after and during the login process.

One particular advantage of metadata is that it’s stored with the user’s account rather than on the device. If the user switches from the mobile version of an app to its web version (or vice versa), their metadata-based settings and preferences will be the same in both versions.

Better still, Auth0’s Actions — custom Node-based code that you can add to your tenant — can use your users’ metadata to perform tasks during many Auth0 workflows, such as when they log in, register for an account, or change their password.

To give you a better idea of what your apps can do with metadata, the app you’ll make in this article’s exercise uses two kinds of metadata:

  • User metadata, which the user can see and update. It’s meant to store user preferences, settings, and other data that do not impact the core functionality available to the user.
  • App metadata, which the user cannot see but is accessible by the application. It’s for storing settings and other data that do impact the application’s core functionality, such as tracking user activity and determining what features or activities should be available to the user.

In this tutorial, you’ll add metadata features to a “starter” app that lets a user view and update their user metadata and displays an announcement if the user’s app metadata contains a specific value.

The Metadata Demo App

Let’s tour the app you’ll work on in this exercise!

When you launch the app, the initial screen greets you with the app’s title, Metadata demo, and a Log in button:

Opening screen, with “Metadata demo” in title font and “Log in” button below it.

Press the Log in button. This will take you to the Auth0 Universal Login screen, which appears in a web browser view embedded in the app:

Universal Login screen, part 1: Entering the email address.

When you use Auth0 to add login/logout capability to your apps, you delegate authentication to an Auth0-hosted login page. You've seen this in action if you use Google web applications such as Gmail and YouTube. These services redirect you to log in using accounts.google.com. After logging in, Google returns you to the web application as a logged-in user.

If you’re worried that using Auth0’s Universal Login means that your app’s login screen will be stuck with the default Auth0 “look and feel,” I have good news for you: you can customize it to match your app or organization’s brand identity.

The Universal Login page saves you from having to code your own authentication system. It gives your applications a self-contained login box with several features to provide a great user experience.

Once you’ve entered your email address, you’ll proceed to the second part of the Universal Login screen, where you’ll enter your password:

Universal Login screen, part 2: Entering the password.

Whenever a user uses Universal Login to authenticate into an app for the first time, they see this screen, which asks for permission to use information from their user account:

Universal Login screen, part 4: “Authorize App” screen.

Here’s what the app is asking for permission to access:

  • Profile: Basic information about the user:
    • The user’s full name, email address, and photo, which the app will display while the user is logged in.
    • The user’s unique identifier. The app will use this to request the items below.
  • Current_user: Additional information about the user, including additional names (first name, last name, nickname, etc.), additional contact information (phone number), and if their email address was verified. The app won’t use this information but needs authorization to access this data in order to access the user metadata.
  • Currentusermetadata: These are the user metadata and app metadata attached to the user’s account. The app will use the user metadata to store a user preference that the user can edit and use the app metadata to determine the content of a button, the URL for the web to open if the button is pressed, and if the button should be visible.

Press the Accept button to continue. This completes the login process, taking you to the app’s main screen, which displays the information from your user account:

“You’re logged in!” screen for user “randomuser@example.com,” showing the user’s name, email, and empty “personal affirmation” text field.

As you can see, the app displays your name, email address, and photo automatically generated for your account when you created it.

It also displays your personal affirmation — user metadata — that you can edit. The text field will be empty if you haven’t entered an affirmation yet. If you have, it will appear in the text field when the app starts up and whenever you press the Refresh affirmation button:

“You’re logged in!” screen for user “randomuser@example.com,” showing the user’s name, email, and filled-in “personal affirmation” text field.

The app also uses app metadata to determine if it should show an “announcement” web button. The app metadata also specifies the text for the button and the URL for the web page it should open:

“You’re logged in!” screen for user “skippy@example.com,” showing the user’s name, email, filled-in “personal affirmation” text field, and “Tap here for an important announcement” button.

If you press the Tap here for an important announcement button, the app opens a YouTube video containing that announcement, delivered by 1980s pop star Rick Astley:

Rick Astley’s “Never Gonna Give You Up” video on YouTube.

When you press the Log out button, you go back to the app’s initial screen, which now displays “You’re logged out” as the title text:

“Logged out” screen, with “You’re logged out.” in title font and “Log in” button below it.

Prerequisites

You’ll need the following to build the app:

1. An Auth0 account

The app uses Auth0 to authenticate users, which means that you need an Auth0 account. You can sign up for a free account, which lets you add login/logout to 10 applications, with support for 7,000 users and unlimited logins. This should suit your prototyping, development, and testing needs.

2. An Android development setup

  • Any computer running Linux, macOS, or Windows from 2013 or later with at least 8 GB RAM. When it comes to RAM, more is generally better.
  • Java SE Developer Kit (JDK), version 11 or later. You can find out which version is on your computer by opening a command-line interface and entering java --version.
  • Android Studio, version 3.6 (February 2020) or later. I used the current stable version of Android Studio when writing this article: version 2021.2.1, also known as “Chipmunk.”
  • At least one Android SDK (Software Development Kit) platform. You can confirm that you have one (and install one if you don’t) in Android Studio. Open ToolsSDK Manager. You’ll see a list of Android SDK platforms. Select the current SDK (Android 11.0 (R) at the time of writing), click the Apply button, and click the OK button in the confirmation dialog that appears. Wait for the SDK platform to install and click the Finish button when installation is complete.

3. An Android device, virtual or real

  • Using a real device: Connect the device to your computer with a USB cable. Make sure that your device has Developer Options and USB debugging enabled.
  • Using a virtual device: Using Android Studio, you can build a virtual device (emulator) that runs on your computer. Here’s my recipe for a virtual device that simulates a current-model inexpensive Android phone:
    1. Open ToolsAVD Manager (AVD is short for “Android Virtual Device”). The Your Virtual Devices window will appear. Click the Create Virtual Device... button.
    2. The Select Hardware window will appear. In the Phone category, select Pixel 3a and click the Next button.
    3. The System Image window will appear, and you’ll see a list of Android versions. Select R (API 30, also known as Android 11.0). If you see a Download link beside R, click it, wait for the OS to download, then click the Finish button. Then click the Next button.
    4. The Android Virtual Device (AVD) window will appear. The AVD Name field should contain Pixel 3a API 30; the two rows below it should have the titles Pixel 3a (a reasonable “representative” phone, released three years ago at the time of writing) and R, and in the Startup orientation section, Portrait should be selected. Click the Finish button.
    5. You will be back at the Your Virtual Devices window. The list will now contain Pixel 3a API 30, and that device will be available when you run the app.

4. A little familiarity with Android development

If you’re new to Android development or the Kotlin programming language, you might find Android Basics in Kotlin to be a good introduction.

First Steps

Download the starter project

To keep this tutorial focused on adding user and app metadata support to Auth0-based authentication in an Android app, I created a starter project that you can download. This app allows the user to log in, see their name, email address, and picture, then log out. Using the starter project, you’ll be able to focus on adding user and app metadata-based features to the app without the distraction of building it from scratch.

🛠 Download the .zip file containing the starter and completed projects for the app and uncompress it. This will create an android-metadata-main folder on your local drive.

🛠 Open the android-metadata-main folder and look for the Android Metadata (starter) folder. Open that folder in Android Studio. Android Studio will spend a little time (depending on your internet connection speed) downloading the Auth0 dependencies for the project.

Don’t run the project yet! It won’t work until you’ve registered the app in Auth0, which you’ll do shortly.

Explore the starter project

The Gradle file for the starter project already includes the Auth0 dependencies: Auth0 and JWTDecode. You don’t have to edit any configuration files.

If you’re building an Android project from scratch and want to incorporate Auth0, you’ll need to add the Auth0 dependencies to the project. You can do this by adding the lines implementation 'com.auth0.android:auth0:2.7.0' and implementation 'com.auth0.android:jwtdecode:2.0.1' to the build.gradle file for the app.

While the starter project is like any Android project and has many files, you’ll edit only three of them:

  • MainActivity.kt: The code behind the app’s one and only activity, which displays its one and only screen in both “logged out” and “logged in” modes. This file is in the project’s /app/src/main/java/com/auth0/androidmetadatademo directory.
  • activity_main.xml: The XML defines the layout of the app’s activity. You’ll find this file in the designated directory for activity layouts: app/src/main/layout.
  • Auth0.xml: A string resource file specifically designated for holding Auth0-related information, namely your tenant’s domain, and the app’s client ID. This file is in the designated directory for “values” resources: app/src/main/res/values.

You might want to explore these files, which contain “helper” and utility objects and functions:

  • User.kt: A data class for extracting and storing the user information from an ID Token.
  • ImageViewExtensions.kt: It contains loadImage(), an extension method that asynchronously loads an image from a given URL into an ImageView.

Both these files are in the project’s /app/src/main/java/com/auth0/androidmetadatademo directory.

Register the Starter App in the Auth0 dashboard

Now that you have the Starter project, it’s time to register its app with Auth0.

🚨 Note: You’ll need an Auth0 account to proceed past this point. 🚨 Once again, you can sign up for an account free of charge.

🛠 In the Auth0 dashboard, click on Applications in the left side menu:

Auth0 dashboard’s “Getting Started” page, with instructions to expand the “Applications” menu.

🛠 This will expand the Applications menu. Select the first item in that menu, which also has the name Applications:

Auth0 dashboard’s “Getting Started” page, with instructions to select the “Applications” menu item.

You will now be on the Applications page. It lists all the applications you have registered to use Auth0 for authentication and authorization.

🛠 Register the app. Do this by clicking the Create application button near the top right of the page:

Auth0 dashboard’s “Applications” page, with instructions to click the “Create Application” button.

This dialog will appear:

“Create Application’ dialog.

🛠 Do the following to continue:

  • Enter a name for the app in the Name field. It might be simplest to use the same name as your Android Studio project, Android Metadata.
  • Specify the application type, which in this case is Native.

🛠 Click Create. The Quick Start page for the app will appear. Click the Settings tab:

The “Settings” page for the Android Metadata applications, with instructions to click on the “Settings” tab.

While you’re at the top of the Settings page, do the following:

🛠 Copy the contents of the Domain field.

🛠 Switch to Android Studio, open the Auth0.xml resource file, and paste the value you just copied as the content of the com_auth0_domain tag.

🛠 Switch back to the Settings page and copy the contents of the Client ID field.

🛠 Switch to Auth0.xml resource file in Android Studio and paste the value you just copied as the content of the com_auth0_client_id tag.

The starter project in Android Studio, with Auth0.xml in the editing view. There are instructions to paste the “Domain” and “Client ID” values from the dashboard into their corresponding XML elements here.

🛠 Switch back to the Settings page and scroll down to the Application URIs section. You’ll need to construct a URL to paste into the Allowed Callback URLs and Allowed Logout URLs fields.

The app’s “Settings” page in the Auth0 dashboard with the instructions “You need to fill these fields” for the “Allowed Callback URLs” and “Allowed Logout URLs” text values.

🛠 Start constructing the URL by pasting the following into both the Allowed Callback URLs and Allowed Logout URLs fields:

{SCHEME}://{YOUR_DOMAIN}/android/{YOUR_APP_PACKAGE_NAME}/callback

🛠 Replace {SCHEME} with app in both the Allowed Callback URLs and Allowed Logout URLs fields. {SCHEME} is the URL’s protocol, and if you were writing a web app, this value would be http, or better, https. Since this is an Android-native app, you can pick any string for this value. I like to use app.

🛠 In both the Allowed Callback URLs and Allowed Logout URLs fields, replace {YOUR_DOMAIN} with the value from the Domain field you saw earlier on this page.

🛠 Replace {YOUR_APP_PACKAGE_NAME} with the app’s bundle identifier into both the Allowed Callback URLs and Allowed Logout URLs fields. If you didn’t change the package name in the starter project, this value is com.auth0.androidmetadatademo.

🛠 Scroll down to the bottom of the page and click the Save Changes button:

The bottom of the app’s “Settings” page in the Auth0 dashboard with  instructions to click the “Save” button.

Create a new user

This exercise works best with a user with specific user and app metadata, so let’s create one.

🛠 In the menu on the left side of the Auth0 dashboard, click on User Management:

The Auth0 dashboard, with instructions to expand the “User Management” menu.

🛠 This will expand the User Management menu. Select the Users item in that menu:

The Auth0 dashboard, with instructions to select the “Users” menu item.

The Users page will appear. It lists all the users registered to your tenant. You’ll see the “You don’t have any users yet” message if there are no users.

The “Users” page with instructions to click the “Create User” button.

🛠 Click the Create User button to create a new user, which will make this dialog box appear:

The “Create User” dialog.

🛠 Enter an email address and password for the user. The only option for the Connection will be Username-Password-Authentication, so leave it as it is. Make a note of that email address and password — you’ll be using them to log in to the app.

🛠 Click the Create button to create the user. The user’s Details page will appear:

The user’s “Details” page.

🛠 Scroll down to the Metadata section:

The “Metadata” section of the user page with two large blank text areas labeled “user_metadata” and “app_metadata”.

🛠 Paste this JSON into the user\metadata_ text area:

{
  "personal_affirmation": "Believe in yourself!"
}

You’ll update the app to receive this JSON, extract the personal_affirmation value, and display it in a text field. You’ll also give the app the ability to update this value, and you’ll be able to confirm that the update was successful by checking its value here.

🛠 Paste this JSON into the app\metadata_ text area:

{
  "display_announcement": true,
  "announcement_text": "Tap here for an important announcement",
  "announcement_url": "https://www.youtube.com/watch?v=DLzxrzFCyOs"
}

You’ll update the app to receive this JSON, extract the display_announcement, announcement_text, and announcement_url values, and use them to display a button with the given text and URL if display_announcement’s value is true. You’ll be able to change the button and whether it appears by altering these values.

🛠 Click the Save button below the appmetadata_ text area to save the metadata you just entered.

Keep the dashboard window or tab open in your browser; you’ll use it to check the user metadata values later.

Run the starter project

🛠 Confirm that the starter project works. Select a simulator or device from the device menu, run the app, and log in.

When logged in, the app should display the following:

The app’s “Logged in” screen displays the user’s photo, name, and email address, as well as the “Log out” button.

The starter project app simply displays the user’s name, email address, and picture.

Look at the ID Token, Access Token, and scopes

Before you begin updating the starter app to use the user’s metadata, let’s take a quick look at a couple of things you’ll need to access that metadata.

🛠 Update the login() method as shown below:

    private fun login() {
        WebAuthProvider
            .login(account)
            .withScheme(getString(R.string.com_auth0_scheme))
            .start(this, object : Callback<Credentials, AuthenticationException> {

                override fun onFailure(exception: AuthenticationException) {
                    // The user either pressed the “Cancel” button
                    // on the Universal Login screen or something
                    // unusual happened.
                    showSnackBar(getString(R.string.login_failure_message))
                }

                override fun onSuccess(credentials: Credentials) {
                    // The user successfully logged in.
                    val idToken = credentials.idToken
                    user = User(idToken)
                    userIsAuthenticated = true
                    // New code 👇🏽👇🏽👇🏽 (requires importing android.util.Log)
                    Log.d("✅ login", "ID Token: ${credentials.idToken}")
                    Log.d("✅ login", "Access Token: ${credentials.accessToken}")
                    Log.d("✅ login", "Scopes: ${credentials.scope}")
                    // New code 👆🏽👆🏽👆🏽
                    showSnackBar(getString(R.string.login_success_message, user.name))
                    updateUI()
                }
            })
    }

It may seem strange to include a ✅ emoji in the tag argument of the calls to Log.d(), but doing so makes it easier to see its output.

🛠 Run the app and log in.

🛠 View the output from the lines you just added to login() by doing the following:

  • Open Android Studio’s logcat window (select ViewTool WindowsLogcat from the menu bar).
  • Select Debug from the drop-down menu of log message types to limit the messages that logcat displays to debug messages.
  • Enter login in the search text field to limit logcat’s output to only those debug messages containing login in either their tags or message bodies.

The logcat window should look like this:

The Android Studio logcat window, with instructions to select “Debug” from the dropdown menu, enter “login” into the search text field, and look for the checkmark emojis.

You should see the following inside the logcat window:

ID Token: {a big string of 1000+ characters}
✅ Access Token: {a big string of 400+ characters}
✅ Scopes: openid profile email

When the user successfully logs in, Auth0 returns a Credentials instance containing a number of properties. The Log.d() statements you just added to login() are displaying the values of the three properties that you’ll need to use to access the user’s metadata:

ID Token icon

ID Token: This is proof that the user is authenticated. It looks like a string of over a thousand random characters, but it’s really a JSON web token (or JWT for short) that’s been base 64 encoded.

The ID Token has information that identifies the user encoded within it. This includes the user’s name, email address, and the URL for their picture; the starter app displays this information when the user is logged in.

The ID Token also contains a unique identifier for the user, which you’ll use in combination with the Access Token to request the user’s metadata from Auth0.

Access Token icon

Access Token: This contains the authorizations for the app to perform specific actions on behalf of the user. These authorizations are in the scope property (see Scopes, below). Like the ID Token, the Access Token looks like a string of random characters but isn’t as long (400+ characters, as opposed to the ID Token’s 1000+).

You’ll use the Access Token with the user’s unique identifier from the ID Token to get the metadata.

You can find out more about ID and Access Tokens in our article, ID Token and Access Token: What's the Difference?

Scope icon

Scopes: These are authorizations specified in the Access Token are defined by the scopes, which define the specific actions that the app can perform on the user’s behalf. The scope property contains scopes granted to the app as a set of space-delimited strings. The scopes currently granted to the app are:

  • openid: Authorization to use OpenID connect to verify the user’s identity. This scope is required; without it, you can’t use Auth0 to authenticate users.
  • profile: Authorization to access the basic information in the user’s profile. This is most often used to get access to the user’s name.
  • email: Authorization to access the user’s email address.

These are the default scopes granted to the app if it does not explicitly request scopes. To access the metadata from the user’s profile, the app needs to request some additional scopes.

Since scopes are authorizations for the app to do things on the user’s behalf, the app must announce that the user is doing so and ask for the user’s approval. That’s why the Universal Login shows the Authorize App screen when a user is logging into an app for the first time.

If you’re new to authentication (or just need a refresher), scopes and their cousins — permissions and privileges — can be confusing. Luckily, our regular series, The Confused Developer, makes them easy to understand in this article: Permissions, Privileges, and Scopes.

The “Personal Affirmation” Section

It’s time to make the first change to the app and implement the app’s “personal affirmation” section. Since users should be able to access their personal affirmation, it will be stored as user metadata, which the Android Auth0 library can read from and write to.

Add the “Personal Affirmation” section’s onscreen controls

🛠 Open activity_main.xml, find the <!-- “Personal affirmation” section goes here --> comment and replace it with the following:

<!-- [ 📄 activity_main.xml ] -->

            <LinearLayout
                android:id="@+id/layout_personal_affirmation"
                android:visibility="invisible"
                android:orientation="vertical"
                android:gravity="center"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="-30dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="50dp"
                    android:text="@string/textview_affirmation_label" />

                <EditText
                    android:id="@+id/edittext_personal_affirmation"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:hint="@string/edittext_affirmation_hint" />

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center"
                    android:orientation="horizontal"
                    android:weightSum="10">

                    <Button
                        android:id="@+id/button_refresh_affirmation"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_weight="3"
                        android:layout_margin="10dp"
                        android:text="@string/button_refresh" />

                    <Button
                        android:id="@+id/button_save_affirmation"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_weight="3"
                        android:layout_margin="10dp"
                        android:text="@string/button_save" />

                </LinearLayout>

            </LinearLayout>

The code you just entered adds a vertical LinearLayout to the app’s view containing the following:

  • A TextView that acts as the “Personal affirmation” label.
  • An EditText that displays the user’s personal affirmation.
  • A horizontal LinearLayout containing:
    • A Refresh button that gets the latest version of the user’s user metadata when pressed
    • A Save button that writes the text in the text field as the value for the user’s personal affirmation to their user metadata

Update MainActivity’s properties

🛠 Update MainActivity’s properties, located near its start, as shown below:

// [ 📄 MainActivity.kt ]

// [ More code here ]

class MainActivity : AppCompatActivity() {

    // Access to onscreen controls
    private lateinit var binding: ActivityMainBinding

    // Auth0 data
    private lateinit var account: Auth0
    private var user = User()
    // New code 👇🏽👇🏽👇🏽
    private var accessToken = ""
    // New code 👆🏽👆🏽👆🏽

    // App and user status
    private var isJustLaunched = true
    private var userIsAuthenticated = false

// [ More code here ]

You just added a new property to MainActivity:

  • accessToken: A string containing the user’s Access Token. The app must provide the Access Token and the user’s ID to retrieve or update the user’s metadata.

Add listeners for two new buttons to onCreate()

🛠 Update the onCreate() method to the following:

// [ 📄 MainActivity.kt ]

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setMainActivity(binding.root)

        account = Auth0(
            getString(R.string.com_auth0_client_id),
            getString(R.string.com_auth0_domain)
        )

        binding.buttonLogin.setOnClickListener { login() }
        binding.buttonLogout.setOnClickListener { logout() }
        // New code 👇🏽👇🏽👇🏽
        binding.buttonRefreshAffirmation.setOnClickListener { getMetadata() }
        binding.buttonSaveAffirmation.setOnClickListener { updateUserMetadata() }
        // New code 👆🏽👆🏽👆🏽

        updateUI()
    }

These two new lines connect the new buttons you added — the Refresh and Save buttons — to the getMetadata() and updateUserMetadata() methods, respectively. You’ll implement these methods soon.

Update the login() method

🛠 Make updates to MainActivity’s login() method as shown below:

// [ 📄 MainActivity.kt ]

// [ More code here ]

    private fun login() {
        WebAuthProvider
            .login(account)
            .withScheme(getString(R.string.com_auth0_scheme))
            // New code 1 👇🏽👇🏽👇🏽
            .withAudience("https://${getString(R.string.com_auth0_domain)}/api/v2/")
            .withScope("openid profile email read:current_user update:current_user_metadata")
            // New code 1 👆🏽👆🏽👆🏽
            .start(this, object : Callback<Credentials, AuthenticationException> {

                override fun onFailure(exception: AuthenticationException) {
                    // The user either pressed the “Cancel” button
                    // on the Universal Login screen or something
                    // unusual happened.
                    showSnackBar(getString(R.string.login_failure_message))
                }

                override fun onSuccess(credentials: Credentials) {
                    // The user successfully logged in.
                    val idToken = credentials.idToken
                    user = User(idToken)
                    // New code 2 👇🏽👇🏽👇🏽
                    accessToken = credentials.accessToken
                    getMetadata()
                    // New code 2 👆🏽👆🏽👆🏽
                    userIsAuthenticated = true
                    Log.d("✅ login", "ID Token: ${credentials.idToken}")
                    Log.d("✅ login", "Access Token: ${credentials.accessToken}")
                    Log.d("✅ login", "Scopes: ${credentials.scope}")
                    showSnackBar(getString(R.string.login_success_message, user.name))
                    updateUI()
                }
            })
    }
  
// [ More code here ]

You’ve added a new code to the login() method in two different places. Let’s look at them in order:

  1. New code 1: To work with the user’s user metadata, the app needs authorization to access the Auth0 Management API. The two method calls that you added to the WebAuthProvider object’s authorization chain do this:
    • The withAudience() method call specifies the API that the app will call using the Access Token. Its argument is the URL for the Auth0 Management API for your app’s tenant, and your tenant’s domain is part of that URL.
    • The withScope() method call specifies the scopes that the app is requesting. In addition to the three default scopes, it has added these two:
      • read:current_user: Authorization to read the user’s user and app metadata
      • update:current_user_metadata: Authorization to update the user’s user metadata
  2. New code 2: Other methods in the activity will need the Access Token value in order to get and update the user’s metadata. The Access Token is one of the properties of the Credentials instance, and copying it into the activity’s accessToken property makes it available to other methods. You’ll then call getMetadata(), which uses the Access Token to retrieve the user’s metadata.

Add a method to get the user’s metadata

🛠 Add a new method — getMetadata() — to MainActivity just after the login() method and before the logout() method:

// [ 📄 MainActivity.kt ]

// [ More code here ]

    private fun getMetadata() {
        // 1
        if (accessToken == "") {
            return
        }

        // 2
        val usersClient = UsersAPIClient(account, accessToken)

        // 3
        usersClient
            .getProfile(user.id)
            .start(object : Callback<UserProfile, ManagementException> {
                
                // 4
                override fun onFailure(exception: ManagementException) {
                    showSnackBar(getString(R.string.general_failure_with_exception_code,
                        exception.getCode()))
                }

                // 5
                override fun onSuccess(userProfile: UserProfile) {
                    // Get user metadata
                    val userMetadata = userProfile.getUserMetadata()
                    val personalAffirmation = userMetadata
                        .getOrDefault("personal_affirmation",
                            "") as String
                    binding.edittextPersonalAffirmation.setText(personalAffirmation)

                    updateUI()
                }

            })
    }

// [ More code here ]

The annotations below correspond to the numbered comments in the code above:

  1. getMetadata() confirms that there is an Access Token in MainActivity’s accessToken property before continuing.
  2. This code creates a UsersAPIClient instance to access the Auth0 Management API.
  3. This code uses that UsersAPIClient instance to fetch the user profile information about the user, including their metadata. It initiates the request to get this profile by calling the following methods in a chain:
    • getProfile() specifies the unique identifier of the user whose profile we want.
    • start() initiates the process of retrieving the user profile. As its final argument, it takes a closure that handles the success and failure cases for metadata retrieval.
  4. The app simply displays an error message in a Snackbar control if it cannot retrieve the user profile.
  5. If the app successfully retrieves the user profile, it extracts the metadata from the profile. Then it attempts to extract the value corresponding to the personal_affirmation key from the metadata. Finally, it updates the contents of the “personal affirmation” text field.

Add a method to update the user’s metadata

When the user presses the Save button, the app should update the user’s user metadata with the contents of the “personal affirmation” text field. The following method will do that.

🛠 Add another method — updateUserMetadata() — to MainActivity just after the getMetadata() method and before the logout() method:

// [ 📄 MainActivity.kt ]

// [ More code here ]

    private fun updateUserMetadata() {
        // 1
        if (accessToken == "") {
            return
        }

        // 2
        val usersClient = UsersAPIClient(account, accessToken)
        // 3
        val metadata = mapOf("personal_affirmation" to binding.edittextPersonalAffirmation.text.toString().trim())

        // 4
        usersClient
            .updateMetadata(user.id, metadata)
            .start(object : Callback<UserProfile, ManagementException> {

                override fun onFailure(exception: ManagementException) {
                    showSnackBar(getString(R.string.general_failure_with_exception_code,
                        exception.getCode()))
                }

                override fun onSuccess(profile: UserProfile) {
                    showSnackBar(getString(R.string.general_success_message))
                }

            }) // start()
    }

// [ More code here ]

The annotations below correspond to the numbered comments in the code above:

  1. updateUserMetadata() confirms that there is an Access Token in MainActivity’s accessToken property before continuing.
  2. This code creates a UsersAPIClient instance to access the Auth0 Management API.
  3. This creates a Map of the key-value pairs that to be updated in the user’s user metadata. In this case, only one key-value pair is updated, where the key is personal_affirmation and the value is the contents of the text field, trimmed of leading and trailing whitespace.
  4. This code uses that UsersAPIClient instance to update the user’s user metadata by calling the following methods in a chain:

    • updateMetadata() updates the user metadata of the user specified by the given unique identifier with the key-value pairs in the given map.
    • start() initiates the updating of the user metadata. As its final argument, it takes a closure that handles the success and failure cases for the process. Both the success and failure cases simply display a Snackbar that displays the outcome.

Update the updateUI() method

🛠 Update MainActivity’s updateUI() method as shown below:

// [ 📄 MainActivity.kt ]

    private fun updateUI() {
        // Title
        if (isJustLaunched) {
            binding.textviewTitle.text = getString(R.string.initial_title)
            isJustLaunched = false
        } else {
            binding.textviewTitle.text = if (userIsAuthenticated) {
                getString(R.string.logged_in_title)
            } else {
                getString(R.string.logged_out_title)
            }
        }

        // Log in / log out buttons
        binding.buttonLogin.isVisible = !userIsAuthenticated
        binding.buttonLogout.isVisible = userIsAuthenticated

        // User information section
        binding.layoutUser.isVisible = userIsAuthenticated
        if (userIsAuthenticated) {
            binding.imageviewUser.loadImage(user.picture)
            binding.textviewUserProfile.text = getString(
                R.string.user_profile,
                user.name,
                user.email
            )
        }

        // New code 👇🏽👇🏽👇🏽
        // Personal affirmation section
        binding.layoutPersonalAffirmation.isVisible = userIsAuthenticated
        // New code 👆🏽👆🏽👆🏽
    }

Try the “personal affirmation” section

🛠 Run the app and log in as the user you created earlier in this exercise.

You’ll see that the app now has a “personal affirmation” section, and the text field contains the value corresponding to the personal_affirmation key that you entered into the user’s user metadata for the user.

🛠 Change the personal affirmation to something different (e.g., “BELIEVE” or “You’ve got this!!!”) and press the Save affirmation button.

🛠 Confirm that the app saved the new affirmation in two different ways:

  • Clear the text field and press the Refresh affirmation button. Your newly-entered affirmation should reappear.
  • Switch to the Auth0 dashboard and look at the user’s user\metadata_ section. You should see your newly-entered affirmation in the user metadata JSON.

The “Announcement” Button

Now that you’ve implemented the app’s “personal affirmation” section, it’s time to add the “announcement” button. The app only shows announcements when necessary, and the app vendors determine this. This decision is based on user information that the user should not be able to see or change, and therefore that information will be stored as app metadata. The Auth0.Android library can read from the user’s app metadata but can’t write to it.

Add the “Announcement” section’s onscreen controls

🛠 Open activity_main.xml, find the <!-- Announcement section goes here --> comment, and replace it with the following:

<!-- [ 📄 activity_main.xml ] -->

            <Button
                android:id="@+id/button_announcement"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:text="" />

Update MainActivity’s properties

🛠 Update MainActivity’s properties, located near its start, as shown below:

// [ 📄 MainActivity.kt ]

// [ More code here ]

class MainActivity : AppCompatActivity() {

    // Access to onscreen controls
    private lateinit var binding: ActivityMainBinding

    // Auth0 data
    private lateinit var account: Auth0
    private var user = User()
    private var accessToken = ""

    // App and user status
    private var isJustLaunched = true
    private var userIsAuthenticated = false
    // New code 👇🏽👇🏽👇🏽
    private var shouldDisplayAnnouncement = false
    private var announcementText = ""
    private var announcementUrl = ""
    // New code 👆🏽👆🏽👆🏽

// [ More code here ]

You just added three more properties to MainActivity. They’re all for the “announcement” button, and they’re all extracted from the user’s app metadata:

  1. shouldDisplayAnnouncement: A boolean that determines if the “announcement” button should be displayed.
  2. announcement: The text of the button.
  3. announcementUrl: The URL for the web page that should open when the user presses the button.

Update the getMetadata() method

🛠 Add a section to MainActivity’s getMetadata() method as shown below:

// [ 📄 MainActivity.kt ]

    private fun getMetadata() {
        // Guard against getting the metadata when the user isn’t logged in.
        if (accessToken == "") {
            return
        }

        val usersClient = UsersAPIClient(account, accessToken)

        usersClient
            .getProfile(user.id)
            .start(object : Callback<UserProfile, ManagementException> {

                override fun onFailure(exception: ManagementException) {
                    showSnackBar(getString(R.string.general_failure_with_exception_code,
                        exception.getCode()))
                }

                override fun onSuccess(userProfile: UserProfile) {
                    // Get user metadata
                    val userMetadata = userProfile.getUserMetadata()
                    val personalAffirmation = userMetadata
                        .getOrDefault("personal_affirmation",
                            "") as String
                    binding.edittextPersonalAffirmation.setText(personalAffirmation)

                    // New code 👇🏽👇🏽👇🏽
                    // Get app metadata
                    val appMetadata = userProfile.getAppMetadata()
                    shouldDisplayAnnouncement = appMetadata
                        .getOrDefault("display_announcement",
                            false) as Boolean
                    if (shouldDisplayAnnouncement) {
                        announcementText = appMetadata.getOrDefault("announcement_text",
                            "") as String
                        announcementUrl = appMetadata.getOrDefault("announcement_url",
                            "") as String
                    }
                    // New code 👆🏽👆🏽👆🏽
                    
                    updateUI()
                }

            })
    }

The newly-added code converts the app metadata received from Auth0 into a Map. It then uses the contents of that Map to set the values of MainActivity’s shouldDisplayAnnouncement, announcementText, and announcementUrl properties.

Update the updateUI() method

🛠 Update MainActivity’s updateUI() method as shown below:

// [ 📄 MainActivity.kt ]

    private fun updateUI() {
        // Title
        if (isJustLaunched) {
            binding.textviewTitle.text = getString(R.string.initial_title)
            isJustLaunched = false
        } else {
            binding.textviewTitle.text = if (userIsAuthenticated) {
                getString(R.string.logged_in_title)
            } else {
                getString(R.string.logged_out_title)
            }
        }

        // Log in / log out buttons
        binding.buttonLogin.isVisible = !userIsAuthenticated
        binding.buttonLogout.isVisible = userIsAuthenticated

        // User information section
        binding.layoutUser.isVisible = userIsAuthenticated
        if (userIsAuthenticated) {
            binding.imageviewUser.loadImage(user.picture)
            binding.textviewUserProfile.text = getString(
                R.string.user_profile,
                user.name,
                user.email
            )
        }

        // Personal affirmation section
        binding.layoutPersonalAffirmation.isVisible = userIsAuthenticated

        // New code 👇🏽👇🏽👇🏽
        // Announcement section
        binding.buttonAnnouncement.isVisible = userIsAuthenticated &&
                shouldDisplayAnnouncement &&
                announcementText != "" &&
                announcementUrl != ""
        if (shouldDisplayAnnouncement) {
            binding.buttonAnnouncement.text = announcementText
            binding.buttonAnnouncement.setOnClickListener {
                val browserIntent =
                    Intent(Intent.ACTION_VIEW, Uri.parse(announcementUrl))
                startActivity(browserIntent)
            }
        }
        // New code 👆🏽👆🏽👆🏽

    }

The new code draws the announcement button if all these conditions are met:

  • The user is logged in,
  • The app metadata indicates that the app should display the announcement, and
  • Values exist for the button’s text and URL.

Try the “announcement” button

🛠 Run the app and log in as the user you created earlier in this exercise.

You’ll see that the app now has a Tap here for an important announcement button.

🛠 Press the button to confirm that it takes you to a special YouTube announcement by one Mr. Rick Astley. I’ll leave determining the importance of the message as an exercise for the reader.

🛠 Switch to the user’s User page on the Auth0 dashboard. Scroll to the app\metadata section and change the value of `displayannouncementtofalse`. Log out, log in again, then confirm that the “announcement” button no longer appears.

Conclusion

Congratulations! You’ve completed the exercise, taking a basic Android app that uses Auth0 authentication and added support for user metadata.

What you did

You used two WebAuthProvider methods: the withAudience() method to specify that you wanted to use the Auth0 Management API and the withScope() method to request not just the default authorizations for logging in but also the authorizations to read the user’s user metadata and app metadata and to update the user’s user metadata.

You extracted the Access Token from the credentials that Auth0 returns when the user successfully logs in. With the user’s unique ID, you used that token to gain the authorization to access the metadata associated with the user’s profile.

You used the user’s user metadata to give them the ability to set and update a user preference: their personal affirmation, which is displayed whenever they’re logged in. You also used the user’s app metadata to determine if the user should see an “announcement” button, as well as what that button says and the web page it should open when pressed.

Get the starter and complete projects

You can find the starter and completed projects in the android-metadata repository on the Auth0 Blog Samples GitHub account.

Experiment!

  • Go to the user’s profile page in the Auth0 dashboard and change the values of announcement_text and announcement_url in the app\metadata_ section.
  • Play with the scopes provided to the scope() method call in the login() method. What functionality goes missing when you change the argument to "openid profile email read:current_user", or the default value of "openid profile email"?
  • Think of other ways to use metadata: to allow the user to choose the app’s color scheme, typeface, or font size. To determine if this is the first time the user has run the app since installing it. To determine if this is a free or paid user.

Do some additional reading

For more about metadata, see Understand How Metadata Works in User Profiles in the Auth0 documentation.

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon