How to secure Python Flask Web APIs with Azure AD
Learn to use identities and tokens in web apps and Azure SQL
1. Introduction
Python Flask is a popular tool to create web applications. Using Azure AD, users can authenticate to the REST APIs and retrieve data from Azure SQL. In this blog, a sample Python web application is created as follows:
- 1a: User logs in to web app and acquires a token
- 1b: User calls a REST API to request a dataset
- 2: Web app uses claims in token to verify user access to dataset
- 3: Web app retrieves data from Azure SQL. Web app can be configured such that either the a) managed identity of the app or b) signed-in user identity is used for authentication to the database
The code of the project can be found here, architecture can be found below.
In the remaining of this blog, the following steps are executed:
- Step 1: Acquire token and call api using token
- Step 2: Verify claims in token
- Step 3a: App managed identity authentication
- Step 3b: Signed-in user passthrough authentication
To learn how to access an Azure Function backend using delegated or application permissions, see my follow-up blog which shares the same git repo as this blog.
Step 1: Acquire token and call api using token
This sample shows how to build a Python web app using Flask and MSAL Python, that signs in a user, and get access to Azure SQL Database. For more information about how the protocols work in this scenario and other scenarios, see Authentication Scenarios for Azure AD. In this step, the following sub steps are executed:
- 1.1: Preliminaries
- 1.2: Create and configure app registration
- 1.3: Configure the python webapp project
- 1.4: Run the sample
Step 1 focusses on the follow part of the architecture.
1.1: Preliminaries
To run this sample, you’ll need:
- Python 2.7+ or Python 3+
- An Azure Active Directory (Azure AD) tenant. For more information on how to get an Azure AD tenant, see how to get an Azure AD tenant.
- Git to clone the following project:
git clone https://github.com/rebremer/ms-identity-python-webapp-backend.git
or download and extract the repository .zip file.
1.2: Create and configure app registration
Create and configure an app registration as follows:
- Create an app registration using the steps in this link to create an app registration. Two remarks:
- Use
http://localhost/getAToken
as reply URL. In case you did not do this during creation, it can be added using the Authentication tab of the app registration - Go to Authentication and enable the option ID tokens in Implicit grant
- Go to Certificates & Secrets to create a secret. Copy the client_id and client secret
1.3: Configure the pythonwebapp project
- Open the
app_config.py
file and change the variables below. - Find text
<<Enter_the_Client_Secret_here>>
and replace it with your application secret during the creation of the app registration in step 1.2. - Find text
<<Enter_the_Tenant_Name_Here>>
and replace the existing value with your Azure AD tenant name. - Find text
<<Enter_the_Application_Id_here>>
and replace the existing value with the application ID (clientId) of the app registration in step 1.2.
1.4: Run the sample
You will need to install dependencies using pip as follows:
$ pip install -r requirements.txt
Run app.py from shell or command line using the following command:
flask run --host localhost --port 5000
When the app is run locally, it can be visited by localhost:5000 (not 127.0.0.1:5000). After step 1, users can login using their Azure AD credentials. In the next step, the user roles are set that can be used to verify if user is allowed to retrieve data using the API.
Step 2: Verify claims in token
In this step, the claims in the tokens can be set which can be just be the web app to verify whether a user is allowed to call an api. See this link for more information on token claims. The following sub steps are executed:
- 2.1: Set configuration in app config
- 2.2: Add roles to manifest
- 2.3: Assign user to role
Step 2 focusses on the follow part of the architecture.
2.1: Set configuration in app config
Claim verification is an optional step and can be enabled using the following setting in app_config.py
file: AAD_ROLE_CHECK = True
.
2.2: Add roles to manifest
Follow the steps in this tutorial to add roles to app registration created in step 1.2. As manifest, the following appRoles shall be used:
"appRoles": [
{
"allowedMemberTypes": ["User"],
"description": "Basic user, only read product data from SQLDB",
"displayName": "basic_user_access",
"id": "a8161423-2e8e-46c4-9997-f984faccb625",
"isEnabled": true,
"value": "basic_user_access"
},
{
"allowedMemberTypes": ["User"],
"description": "Premium user, read all data from SQLDB",
"displayName": "premium_user_access",
"id": "b8161423-2e8e-46c4-9997-f984faccb625",
"isEnabled": true,
"value": "premium_user_access"
}
],
2.3: Assign user to role
The assignment of users is explained in th link. As a test, two users can be created. User 1 is assigned the basic_user_access
, whereas user 2 gets the premium_user_access
role.
In the next step, the Azure SQL database is created and the application identity is used to retrieve data form the database.
Step 3a: App Managed Identity authentication
In this step, the managed identity of the app is used to retrieve data, which is linked to the app registration created in step 1. The following sub steps are executed:
- 3a.1: Create Azure SQL database
- 3a.2: Set configuration in app config
Step 3a focusses on the follow part of the architecture.
3a.1: Create Azure SQL database
Create an Azure SQL DB using this link in which the cheapest SKU (basic) can be selected. Make sure the following is done:
- AdvertureWorks is installed as sample database, the cheapest database can be selected (SKU basic).
- Add app identity as user to Azure SQL database with correct reader roles, see example below as follows
CREATE USER [<<Name of app registration>>] FROM EXTERNAL PROVIDER;
EXEC sp_addrolemember [db_datareader], [<<Name of app registration>>];
3a.2: Set configuration in app config
The backend_settings needs to be set to database. Make also sure that connection is filled in with your settings. Since the MI of the app is used, application_permissions need to point to “https://database.windows.net//.default" in the app_config.py
file, see also below.
# 2. Type of BACKEND
#
# Option 2a. Database
BACKEND_SETTINGS = {"Type": "Database", "Connection":{"SQL_SERVER": "<<Enter_logical_SQL_server_URL_here>>.database.windows.net", "DATABASE": "<<Enter_SQL_database_name_here>>"}}
# Option 3a. Delegated user is used to authenticate to Graph API, MI is then used to authenticate to backend
...
APPLICATION_PERMISSIONS = ["https://database.windows.net//.default"]
Now the app can be run as described in step 1.4. When you click on the link (Premium users only) Get Customer data from Database
, customer data is retrieved. Subsequently, when there is clicked on the link Get Product data from Database
, product data is retrieved (provided that claims are set correctly for user in step 2 or check is disabled)
In this step, the identity of the app is used to retrieve data. However, the identity of the user can also passed (AAD passthrough) to retrieve data from the database.
Step 3b: Signed-in user passthrough authentication
In the step, the identity of the user itself is used to retrieve data. This means that the token created in step 1 to login in the web app is also used to authenticate to the database. The following sub steps are executed:
- 3b.1: Add Azure SQL DB Scope to app registration
- 3b.2: Add AAD user to database
- 3b.3: Set configuration in app config
Step 3b focusses on the follow part of the architecture.
3b.1: Add Azure SQL DB Scope to app registration
- Modify your app registration created in step 1.2. with permissions for Azure SQL database as delegated user. This is explained in this link
- Important: Admin consent is required for Azure SQL Database. This can be either done by selecting Grant_admin consent for Default Directory in the permissions tab or at runtime while logging in
3b.2: Add AAD user to database
Since AAD passthrough is used in this step, the users themselves shall have the appropriate roles in the SQLDB as external user and datareader. See example how to do this below.
CREATE USER [<<AAD user email address>>] FROM EXTERNAL PROVIDER;
EXEC sp_addrolemember [db_datareader], [<<AAD user email address>>];
In case you want to be more granular in rolemembers in the database, read_customer
reads data from SalesLT.Customer, whereas read_product
reads data from SalesLT.Product)
3b.3: Set configuration in app config
AAD User passthrough authentication can be set in the app_config.py
file by setting delegated_permissions to [“https://sql.azuresynapse-dogfood.net/user_impersonation"], see also below
# Option 3b. Delegated user is used to authenticate to backend, graph API disabled
...
DELEGATED_PERMISSONS = ["https://sql.azuresynapse-dogfood.net/user_impersonation"]
Now the app can be run as described in step 1.4, in which data can be retrieved from the database using the identity of the logged in user.
Conclusion
In this blog, a Python web application is created that retrieves data from SQLDB. Users claims, managed identities and signed-in user passthrough tokens are discussed to authenticate and authorize users to retrieve data from Azure SQL, see also overview below.