In this post, I am writing about how to setup Azure Active Directory Android application login to access Azure functions protected with Azure Active Directory authentication.

Create an Android Application

Open Android Studio and create new Project with Empty Activity.

Configure project with below settings

Get SHA1 & package name

Open powershell and cd to C:\Users<username>.android Execute below command, if prompted for password enter android or leave blank

keytool -list -v -keystore debug.keystore -alias androiddebugkey -storepass android -keypass android

Copy SHA1 -> Navigate to https://base64.guru/converter/encode/hex and convert SHA1 to Base64

Copy package name from AndroidManifest.xml

Now you have packagename & Base64 SHA1 hash

Setup Azure Active Directory

I followed steps from Register your app under Azure Active Directory (using Android platform settings) to setup AAD.

Login to Azure
Open AAD
Click App Registration -> New Registration

Select Authentication
Select Add a platform

Enter SHA1 Hash & package name

Copy Android Configuration

Android App Login & Logout implementation

I followed steps from Configure your App Service or Azure Functions app to use Azure AD login

Edit build.gradle and add

implementation 'com.microsoft.identity.client:msal:1.4.+'

packagingOptions{
        exclude("META-INF/jersey-module-version")
}

Edit build.gradle and add

<activity
            android:name="com.microsoft.identity.client.BrowserTabActivity">
            <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="msauth"
                    android:host="com.example.********"
                    android:path="/GH**********I=" />
            </intent-filter>
        </activity>

Configure auth_config_single_account.json as below

Edit activity_main.xml and add

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/loginButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="157dp"
        android:layout_marginTop="46dp"
        android:layout_marginEnd="161dp"
        android:text="Login"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/signoutButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="153dp"
        android:layout_marginTop="82dp"
        android:layout_marginEnd="159dp"
        android:text="Signout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/loginButton" />

</androidx.constraintlayout.widget.ConstraintLayout>

Edit MainActivity.java and add

package com.example.logintestaad;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.microsoft.identity.client.AuthenticationCallback;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.IAuthenticationResult;
import com.microsoft.identity.client.IPublicClientApplication;
import com.microsoft.identity.client.ISingleAccountPublicClientApplication;
import com.microsoft.identity.client.PublicClientApplication;
import com.microsoft.identity.client.exception.MsalException;

public class MainActivity extends AppCompatActivity {

    Button signInButton;
    Button signOutButton;
    private final static String[] SCOPES = {"Files.Read"};
    private ISingleAccountPublicClientApplication mSingleAccountApp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        signInButton = findViewById(R.id.loginButton);
        signOutButton = findViewById(R.id.signoutButton);
        signInButton.setVisibility(View.GONE);
        signOutButton.setVisibility(View.GONE);

        //Sign in user
        signInButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v) {
                if (mSingleAccountApp == null) {
                    return;
                }
                mSingleAccountApp.signIn(MainActivity.this, null, SCOPES, getAuthInteractiveCallback());
            }
        });

        //Sign out user
        signOutButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSingleAccountApp == null){
                    return;
                }
                mSingleAccountApp.signOut(new ISingleAccountPublicClientApplication.SignOutCallback() {
                    @Override
                    public void onSignOut() {
                        signInButton.setVisibility(View.VISIBLE);
                        signOutButton.setVisibility(View.GONE);
                    }
                    @Override
                    public void onError(@NonNull MsalException exception){

                    }
                });
            }
        });

        PublicClientApplication.createSingleAccountPublicClientApplication(getApplicationContext(),
                R.raw.auth_config_single_account, new IPublicClientApplication.ISingleAccountApplicationCreatedListener() {
                    @Override
                    public void onCreated(ISingleAccountPublicClientApplication application) {
                        mSingleAccountApp = application;
                        loadAccount();
                    }
                    @Override
                    public void onError(MsalException exception) {
                        // displayError(exception);
                    }
                });
    }

    private AuthenticationCallback getAuthInteractiveCallback() {
        return new AuthenticationCallback() {
            @Override
            public void onSuccess(IAuthenticationResult authenticationResult) {
                signInButton.setVisibility(View.GONE);
                signOutButton.setVisibility(View.VISIBLE);


            }

            @Override
            public void onError(MsalException exception) {
                /* Failed to acquireToken */

            }
            @Override
            public void onCancel() {

            }
        };
    }

    private void loadAccount() {
        if (mSingleAccountApp == null) {
            return;
        }

        mSingleAccountApp.getCurrentAccountAsync(new ISingleAccountPublicClientApplication.CurrentAccountCallback() {
            @Override
            public void onAccountLoaded(@Nullable IAccount activeAccount) {
                // You can use the account data to update your UI or your app database.
                if(activeAccount == null)
                {
                    signInButton.setVisibility(View.VISIBLE);
                    signOutButton.setVisibility(View.GONE);
                }
                else {
                    signOutButton.setVisibility(View.GONE);
                    signInButton.setVisibility(View.VISIBLE);
                }

            }

            @Override
            public void onAccountChanged(@Nullable IAccount priorAccount, @Nullable IAccount currentAccount) {
                if (currentAccount == null) {
                    // Perform a cleanup task as the signed-in account changed.
                }
            }

            @Override
            public void onError(@NonNull MsalException exception) {

            }
        });
    }
}

Run application, you should be able to login & Logout

Creating & Publishing Azure Function

Follow below documents to setup Azure Function using Visual Studio or Visual Studio Code.

Develop Azure Functions by using Visual Studio Code Develop Azure Functions by using Visual Studio Code

Once Azure function is hosted, verify the URL response in browser.

https://<yourfunctionanme>.azurewebsites.net/api/<functionparameter>

Protect Azure Function with Azure Active Directory Authentication

Follow document Configure your App Service or Azure Functions app to use Azure AD login

Open function in Azure and select Authentication
Enable On
Select Log in with Azure Active Directory for Action to take when request is not authenticated
Click Azure Active Directory as Authentication Provider
Click Advanced
Enter client id from andridLoginTest Azure Directory Application setup (Setup Azure Active Directory)
Enter Issuer Url as https://sts.windows.net/<tokenid>
Click Allowed Token Audience and add your function URL (do not add / or api/, just base URL)

Setup Azure Active Directory (AAD) API Expose and Scopes

Open Azure Active Directory
Select Branding and setup Home page URL to Azure Function
Click Expose an API -> Add a scope -> set Application ID URI to Azure function URI(https://.azurewebsites.net) -> Save and Continue
In next window Add a scope -> set Scope name as apiaccess-> select Admins and users -> Enter anything in consent name and description
Click API permissions -> Add a Permission -> My API's -> select Name of API (scope name -> apiaccess)-> Add Permission -> Grant admin consent for Default -> yes->save->grant admin consent->yes

Android App Calling function

Edit build.gradle and add

    implementation 'com.android.volley:volley:1.1.1'

Edit AndroidManifest.xml and add

    <uses-permission android:name="android.permission.INTERNET" />

Replace MainActivity.java

package com.example.logintestaad;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.microsoft.identity.client.AuthenticationCallback;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.IAuthenticationResult;
import com.microsoft.identity.client.IPublicClientApplication;
import com.microsoft.identity.client.ISingleAccountPublicClientApplication;
import com.microsoft.identity.client.PublicClientApplication;
import com.microsoft.identity.client.exception.MsalException;
import com.android.volley.RequestQueue;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    private final static String[] SCOPES = {"https://androidaccessfunctiontest20210122095133.azurewebsites.net/apiaccess"};
    Button signInButton;
    Button signOutButton;
    private ISingleAccountPublicClientApplication mSingleAccountApp;
    private RequestQueue queue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        signInButton = findViewById(R.id.loginButton);
        signOutButton = findViewById(R.id.signoutButton);
        signInButton.setVisibility(View.GONE);
        signOutButton.setVisibility(View.GONE);

        //Sign in user
        signInButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                if (mSingleAccountApp == null) {
                    return;
                }
                mSingleAccountApp.signIn(MainActivity.this, null, SCOPES, getAuthInteractiveCallback());
            }
        });

        //Sign out user
        signOutButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSingleAccountApp == null) {
                    return;
                }
                mSingleAccountApp.signOut(new ISingleAccountPublicClientApplication.SignOutCallback() {
                    @Override
                    public void onSignOut() {
                        signInButton.setVisibility(View.VISIBLE);
                        signOutButton.setVisibility(View.GONE);
                    }

                    @Override
                    public void onError(@NonNull MsalException exception) {

                    }
                });
            }
        });

        PublicClientApplication.createSingleAccountPublicClientApplication(getApplicationContext(),
                R.raw.auth_config_single_account, new IPublicClientApplication.ISingleAccountApplicationCreatedListener() {
                    @Override
                    public void onCreated(ISingleAccountPublicClientApplication application) {
                        mSingleAccountApp = application;
                        loadAccount();
                    }

                    @Override
                    public void onError(MsalException exception) {
                        // displayError(exception);
                    }
                });
    }

    private AuthenticationCallback getAuthInteractiveCallback() {
        return new AuthenticationCallback() {
            @Override
            public void onSuccess(IAuthenticationResult authenticationResult) {
                signInButton.setVisibility(View.GONE);
                signOutButton.setVisibility(View.VISIBLE);
                String token = authenticationResult.getAccessToken();
                String tokenGenerationAddress = "https://androidaccessfunctiontest20210122095133.azurewebsites.net/api/callme";

                //cal azure function with access token
                queue = Volley.newRequestQueue(getApplicationContext());

                StringRequest stringRequest = new StringRequest(Request.Method.GET, tokenGenerationAddress,
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                String text = response;
                            }
                        }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        //textView.setText("That didn't work!");
                    }
                }) {
                    @Override
                    public Map<String, String> getHeaders() throws AuthFailureError {
                        HashMap<String, String> headers = new HashMap<String, String>();
                        headers.put("Authorization", "Bearer " + token);
                        return headers;
                    }
                };
                queue.add(stringRequest);
            }

            @Override
            public void onError(MsalException exception) {

            }

            @Override
            public void onCancel() {

            }
        };
    }

    private void loadAccount() {
        if (mSingleAccountApp == null) {
            return;
        }

        mSingleAccountApp.getCurrentAccountAsync(new ISingleAccountPublicClientApplication.CurrentAccountCallback() {
            @Override
            public void onAccountLoaded(@Nullable IAccount activeAccount) {
                // You can use the account data to update your UI or your app database.
                if (activeAccount == null) {
                    signInButton.setVisibility(View.VISIBLE);
                    signOutButton.setVisibility(View.GONE);
                } else {
                    signOutButton.setVisibility(View.VISIBLE);
                    signInButton.setVisibility(View.GONE);
                }

            }

            @Override
            public void onAccountChanged(@Nullable IAccount priorAccount, @Nullable IAccount currentAccount) {
                if (currentAccount == null) {
                    // Perform a cleanup task as the signed-in account changed.
                }
            }

            @Override
            public void onError(@NonNull MsalException exception) {
            }
        });
    }
}

Finally, debug code, you should be able to see response from Azure function.