This repository demonstrates an end-to-end deployment of an Azure Active Directory (AAD) authenticated call to an Azure Function (AF) behind API Management (API-M). The infrastructure is deployed using terraform and the user then starts a WebApp, logs in to AAD, then calls the underlying Azure Function through API-M.
Create a terraform.tfvars
file: touch deployment/terraform/terraform.tfvars
with the following content:
project = "apim2"
environment = "dev"
location = "eastus2"
Note that you may (likely will) need to change the project = apim2
to some other value as it will need to be globally unique.
Then execute the following commands:
cd deployment/terraform
terraform init
terraform plan --var-file terraform.tfvars
terraform apply --auto-approve --var-file terraform.tfvars
Deploying API-M can take anywhere from 40 minutes to up to 1h 40m, so execute the apply, make sure there are no errors (like global naming issues), and go grab some coffee as it may take some time.
You can test the function with the following command:
cd functions
npm i
npm start
Open your browser to [http://localhost:7071/api/hello-world?name=John] and you should see:
{
"name": "John"
}
cd
to the the<repo>/functions
directory- Run:
npm run build
- Run:
func azure functionapp publish apim-dev-function-app --javascript
. Note that you'd need to changeapim
anddev
to theproject
andenvironment
set in the terraform.tfvars
- Go to the terraform directory to extract output
cd deployment/terraform
to prepare for the below command. - Run
az ad app permission admin-consent --id "$(tf output | grep ad_client_app_app_id | awk '{print $3}')"
where the--id
is theclient-app
client id. Alternatively see the admin consent docs for granting through the Azure Portal
The WebApp is forked and modified from the Azure Samples 'ms-identity-javascript-react-tutorial' project.
First you'll need to update the WebApp config.json before starting the webapp.
- Copy the config.example.json to config.json:
cp ms-identity-javascript-react-spa/src/config.example.json ms-identity-javascript-react-spa/src/config.json
- Go to the terraform directory to extract output
cd deployment/terraform
to prepare for the below command. - Copy and paste the following command into your terminal to update the config.json:
CLIENT_ID=$(tf output | grep ad_client_app_app_id | awk '{print $3}') \
AUTHORITY="https://login.microsoftonline.com/$(az account show | jq -r '.tenantId')" \
SCOPES="api://$(terraform output | grep ad_backend_app_display_name | awk '{print $3}')/hello_world" \
FUNCTION_HELLO_WORLD="$(terraform output | grep api_management_gateway_url | awk '{print $3}')/hello-world" \
JSON_CONTENTS=$(jq --arg CLIENT_ID $CLIENT_ID --arg AUTHORITY $AUTHORITY --arg SCOPES $SCOPES --arg FUNCTION_HELLO_WORLD $FUNCTION_HELLO_WORLD '.clientId = $CLIENT_ID | .authority = $AUTHORITY | .scopes = [ $SCOPES ] | .functionHelloWorld = $FUNCTION_HELLO_WORLD' ../../ms-identity-javascript-react-spa/src/config.json) \
echo $JSON_CONTENTS > ../../ms-identity-javascript-react-spa/src/config.json
- Change directory to the webapp:
cd ../../ms-identity-javascript-react-spa
- Verify values have been correctly set in src/config.json
- Install node modules:
npm i
- Start the Webapp:
npm run start
Navigate to [http://localhost:3000]
Click sign in in the upper right.
Then you should see a Welcome message and a Button to Call API Management
.
Click the Call API Management
and you should receive a json response and then see the Bearer token.
The Bearer token is shown here if you want to use jwt.io or jwt.ms to analyze the access token, or if you want to be able to copy and paste it to Test the Azure Function within the API Management interface.
The current design has the API-M validating the access token and passing that token to the Azure Function. The consequence of this is that it would not work in a multi-tenant scenario. For multi-tenant scenarios it's best to use Managed Identity between the API-M API and the underlying Azure Function, that way the Azure Function only accepts auth from API-M and the API-M can then accept any configured client auth. Whether this is desirable or not completely depends on the scenario in question.
It will be fairly common to have naming conflicts here as names for certain services are globally unique. For example you will likely need to rename the API-M name via the terraform.tfvars if you see something like this: