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://
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.