page_type | languages | products | name | description | urlFragment | |||
---|---|---|---|---|---|---|---|---|
sample |
|
|
Python Azure Function Web API secured by Azure AD |
Python Azure Function Web API secured by Azure AD. |
ms-identity-python-webapi-azurefunctions |
This code example demonstrates how to secure an Azure Function with Azure AD when the function uses HTTPTrigger and exposes a Web API. The Web API is written using python.
This readme walks you through the steps of setting this code up in your Azure subscription.
While you can develop Azure Functions in many ways, such as Visual Studio 2019, Visual Studio Code, etc. this guide shows how to perform the steps using Visual Studio Code.
Outline the file contents of the repository. It helps users navigate the codebase, build configuration and any related assets.
File/folder | Description |
---|---|
src |
Sample source code. |
.gitignore |
Define what to ignore at commit time. |
CHANGELOG.md |
List of changes to the sample. |
CONTRIBUTING.md |
Guidelines for contributing to the sample. |
README.md |
This README file. |
LICENSE |
The license for the sample. |
images |
Images used in readme.md. |
Function |
The Azure Function code. |
- You must have Visual Studio Code installed
- You must have Azure Functions core tools installed
npm install -g azure-functions-core-tools
- Azure Functions VSCode extension (https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
Reference: How to register an app
The Azure Function acts as a WebAPI. There are a few things to know here.
- The function app will run on
http://localhost:3000
when you test it locally. - The function app will run on
https://<yourpythonfunction>.azurewebsites.net
when you run it deployed in azure - The function exposes an API with app id uri
https://<yourpythonfunction>.<tenant>.onmicrosoft.com
Note that all these values are configurable to your liking, and they are reflected in the Function/secureFlaskApp/__init__.py
file.
Additionally, you will need a "client" for the Web API. Since this function will serve as a AAD protected Web API, any client that understands standard openid connect flows will work. The usual consent grant principals apply.
Reference: Azure Active Directory consent framework
To keep things simple, we will reuse the same app registration as both the client, and the API. This eliminates any need to provide explicit consent. For our code example here, the client will use auth code flow, for which we will also need a client secret. We are trying to mimic a web app calling this API, and a web app can act as a confidential client.
To setup the app, you can use the below azure CLI script. Note the placeholders demarcated in <..>
brackets. Make sure to replace them with your environment specific values.
az ad app create --display-name "FuncAPI" --credential-description "funcapi" --password "p@ssword1" --reply-urls "http://localhost:3000" --identifier-uris "https://funcapi.<tenantname>.onmicrosoft.com"
For the above registered app, get the app ID
az ad app list --query "[?displayName == 'FuncAPI'].appId"
Also get your tenant ID
az account show --query "tenantId"
Update your Function/secureFlaskApp/__init__.py
with the values per your app registration. Specifically, update the below lines.
API_AUDIENCE = "https://funcapi.<tenantname>.onmicrosoft.com/user_impersonation"
TENANT_ID = "<tenantid>"
- With the project open in VSCode, just hit F5, or you can also run
func host start
from the CLI. - You will need an access token to call this function. In order to get the access token, open browser in private mode and visit
https://login.microsoftonline.com/<tenantname>.onmicrosoft.com/oauth2/v2.0/authorize?response_type=code&client_id=<appid>&redirect_uri=http://localhost:3000/&scope=openid
This will prompt you to perform authentication and consent, and it will return a code in the query string.
Use that code in the following request to get an access token, remember to put in the code and client secret.
I am using the client secret of p@ssword1
as I setup in my scripts above. In production environments, you want this to be more complex.
curl -X POST \
https://login.microsoftonline.com/<tenantname>.onmicrosoft.com/oauth2/v2.0/token \
-H 'Accept: */*' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Host: login.microsoftonline.com' \
-H 'accept-encoding: gzip, deflate' \
-H 'cache-control: no-cache' \
-d 'redirect_uri=http%3A%2F%2Flocalhost:3000&client_id=<appid>&grant_type=authorization_code&code=<put code here>&client_secret=p@ssword1&scope=https%3A%2F%funcapi.<tenantname>.onmicrosoft.com%2F/user_impersonation'
- Once you get the access token, make a GET request to
http://localhost:3000/api
with the access token as a Authorization Bearer header. Verify that you get an output similar to the below. The values marked as ..removed.. will have actual values in your output.
{
"aud": "https://funcapi.<tenantname>.onmicrosoft.com",
"iss": "https://sts.windows.net/<tenantid>/",
"iat": 1571732525,
"nbf": 1571732525,
"exp": 1571736425,
"acr": "1",
"aio": "..removed..",
"amr": [
"pwd"
],
"appid": "..removed..",
"appidacr": "1",
"email": "..removed..",
"family_name": "..removed..",
"given_name": "..removed..",
"idp": "..removed..",
"ipaddr": "..removed..",
"name": "..removed..",
"oid": "..removed..",
"scp": "user_impersonation",
"sub": "..removed..",
"tid": "..removed..",
"unique_name": "..removed..",
"uti": "..removed..",
"ver": "1.0"
}
- Go ahead and create a function app in azure, ensure that you pick python as it's runtime.
- Choose to deploy the function
- You will need an access token to call this function. In order to get the access token, open browser in private mode and visit
https://login.microsoftonline.com/<tenantname>.onmicrosoft.com/oauth2/v2.0/authorize?response_type=code&client_id=<appid>&redirect_uri=https://<yourpythonfunction>.azurewebsites.net/callback&scope=openid
This will prompt you to perform authentication, and it will return a code. Use that code in the following request to get an access token, remember to put in the code and client secret.
curl -X POST \
https://login.microsoftonline.com/<tenantname>.onmicrosoft.com/oauth2/v2.0/token \
-H 'Accept: */*' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Host: login.microsoftonline.com' \
-H 'accept-encoding: gzip, deflate' \
-H 'cache-control: no-cache' \
-d 'redirect_uri=https%3A%2F%2F<yourpythonfunction>.azurewebsites.net%2Fcallback&client_id=<appid>&grant_type=authorization_code&code=<put code here>&client_secret=<put client secret here>&scope=https%3A%2F%2Fmytestapp.<tenantname>.onmicrosoft.com%2Fuser_impersonation'
- Once you get the access token, make a GET request to
https://<yourpythonfunction>.azurewebsites.net/api
with the access token as a Authorization Bearer header. Verify that you get an output similar to the below. The values marked as ..removed.. will have actual values in your output.
{
"aud": "https://funcapi.<tenantname>.onmicrosoft.com",
"iss": "https://sts.windows.net/<tenantid>/",
"iat": 1571732525,
"nbf": 1571732525,
"exp": 1571736425,
"acr": "1",
"aio": "..removed..",
"amr": [
"pwd"
],
"appid": "..removed..",
"appidacr": "1",
"email": "..removed..",
"family_name": "..removed..",
"given_name": "..removed..",
"idp": "..removed..",
"ipaddr": "..removed..",
"name": "..removed..",
"oid": "..removed..",
"scp": "user_impersonation",
"sub": "..removed..",
"tid": "..removed..",
"unique_name": "..removed..",
"uti": "..removed..",
"ver": "1.0"
}
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.