Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FLASK_DEBUG=True
CLIENT_ID=<client id>
CLIENT_SECRET=<client secret>
TENANT_ID=<tenant id>
6 changes: 3 additions & 3 deletions AppCreationScripts/Apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@
],
"codeConfigurations": [
{
"settingFile": "/app_config.py",
"settingFile": "/.env",
"replaceTokens": {
"appId": "Enter_the_Application_Id_here",
"appId": "<client id>",
"tenantId": "common",
"clientSecret": "Enter_the_Client_Secret_Here",
"clientSecret": "<client secret>",
"authorityEndpointHost": "https://login.microsoftonline.com/",
"msgraphEndpointHost": "https://graph.microsoft.com/"
}
Expand Down
9 changes: 7 additions & 2 deletions AppCreationScripts/Configure.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ Function ReplaceInLine([string] $line, [string] $key, [string] $value)

Function ReplaceInTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
{
# Check if text file exists. If not, copy .env.sample.
if (!(Test-Path $configFilePath))
{
Copy-Item "$configFilePath.sample" $configFilePath
}
$lines = Get-Content $configFilePath
$index = 0
while($index -lt $lines.Length)
Expand Down Expand Up @@ -225,9 +230,9 @@ Function ConfigureApplications
Write-Host "Granted permissions."

# Update config file for 'pythonwebapp'
$configFile = $pwd.Path + "\..\app_config.py"
$configFile = $pwd.Path + "\..\.env"
Write-Host "Updating the sample code ($configFile)"
$dictionary = @{ "Enter_the_Tenant_Name_Here" = $tenantName;"Enter_the_Client_Secret_Here" = $pythonwebappAppKey;"Enter_the_Application_Id_here" = $pythonwebappAadApplication.AppId };
$dictionary = @{ "<tenant id>" = $tenantName;"<client secret>" = $pythonwebappAppKey;"<client id>" = $pythonwebappAadApplication.AppId };
ReplaceInTextFile -configFilePath $configFile -dictionary $dictionary

Add-Content -Value "</tbody></table></body></html>" -Path createdApps.html
Expand Down
156 changes: 8 additions & 148 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,162 +9,22 @@ urlFragment: ms-identity-python-webapp
---
# Integrating Microsoft Identity Platform with a Python web application

## About this sample
This is a Python web application that uses the Flask framework and the Microsoft identity platform to sign in users and make authenticated calls to the Microsoft Graph API.

> This sample is also available as a quickstart for the Microsoft identity platform:
[Quickstart: Add sign-in with Microsoft to a Python web app](https://docs.microsoft.com/azure/active-directory/develop/web-app-quickstart?pivots=devlang-python)
To get started with this sample, follow this tutorial:
[Quickstart: Add sign-in with Microsoft to a Python web app](https://docs.microsoft.com/azure/active-directory/develop/web-app-quickstart?pivots=devlang-python).


### Overview
## Using PowerShell scripts

This sample demonstrates a Python web application that signs-in users with the Microsoft identity platform and calls the Microsoft Graph.
The linked tutorial above uses the Azure portal to create the Azure AD applications and related objects.

1. The python web application uses the Microsoft Authentication Library (MSAL) to obtain a JWT access token from the Microsoft identity platform (formerly Azure AD v2.0):
2. The access token is used as a bearer token to authenticate the user when calling the Microsoft Graph.
However, if you're comfortable with PowerShell, you can use PowerShell scripts that automatically create the Azure AD applications and related objects (passwords, permissions, dependencies) for you, and then modify the applications' configuration files. To use this automation, follow the steps in the [App Creation Scripts README](./AppCreationScripts/AppCreationScripts.md).

![Overview](./ReadmeFiles/topology.png)

### Scenario

This sample shows how to build a Python web app using Flask and MSAL Python,
that signs in a user, and get access to Microsoft Graph.
For more information about how the protocols work in this scenario and other scenarios,
see [Authentication Scenarios for Azure AD](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-authentication-scenarios).

## How to run this sample

To run this sample, you'll need:

> - [Python 3](https://www.python.org/downloads/)
> - 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.](https://docs.microsoft.com/azure/active-directory/develop/quickstart-create-new-tenant)


### Step 1: Clone or download this repository

From your shell or command line:

```Shell
git clone https://github.com/Azure-Samples/ms-identity-python-webapp.git
```

or download and extract [the repository .zip file](https://github.com/Azure-Samples/ms-identity-python-webapp/archive/refs/heads/master.zip).

> Given that the name of the sample is quite long, you might want to clone it in a folder close to the root of your hard drive, to avoid file name length limitations when running on Windows.

### Step 2: Register the sample application with your Azure Active Directory tenant

There is one project in this sample. To register it, you can:

- either follow the steps [Step 2: Register the sample with your Azure Active Directory tenant](#step-2-register-the-sample-with-your-azure-active-directory-tenant) and [Step 3: Configure the sample to use your Azure AD tenant](#choose-the-azure-ad-tenant-where-you-want-to-create-your-applications)
- or use PowerShell scripts that:
- **automatically** creates the Azure AD applications and related objects (passwords, permissions, dependencies) for you
- modify the applications' configuration files.

If you want to use this automation:

1. On Windows, run PowerShell and navigate to the root of the cloned directory
1. In PowerShell run:

```PowerShell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
```

1. Run the script to create your Azure AD application and configure the code of the sample application accordingly.
1. In PowerShell run:

```PowerShell
cd .\AppCreationScripts\
.\Configure.ps1
cd ..
```

> Other ways of running the scripts are described in [App Creation Scripts](./AppCreationScripts/AppCreationScripts.md)

If you don't want to use this automation, follow the steps below.

#### Choose the Azure AD tenant where you want to create your applications

As a first step you'll need to:

1. Sign in to the [Azure portal](https://portal.azure.com) using either a work or school account or a personal Microsoft account.
1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory**.
Change your portal session to the desired Azure AD tenant.

#### Register the Python Webapp (python-webapp)

1. Navigate to the Microsoft identity platform for developers [App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) page.
1. Select **New registration**.
1. When the **Register an application page** appears, enter your application's registration information:
- In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `python-webapp`.
- Change **Supported account types** to **Accounts in any organizational directory and personal Microsoft accounts (e.g. Skype, Xbox, Outlook.com)**.
- In the Redirect URI (optional) section, select **Web** in the combo-box and enter the following redirect URIs: `http://localhost:5000/getAToken`.
1. Select **Register** to create the application.
1. On the app **Overview** page, find the **Application (client) ID** value and record it for later. You'll need it to configure the Visual Studio configuration file for this project.
1. Select **Save**.
1. From the **Certificates & secrets** page, in the **Client secrets** section, choose **New client secret**:

- Type a key description (of instance `app secret`),
- Select a key duration of either **In 1 year**, **In 2 years**, or **Never Expires**.
- When you press the **Add** button, the key value will be displayed, copy, and save the value in a safe location.
- You'll need this key later to configure the project in Visual Studio. This key value will not be displayed again, nor retrievable by any other means,
so record it as soon as it is visible from the Azure portal.
1. Select the **API permissions** section
- Click the **Add a permission** button and then,
- Ensure that the **Microsoft APIs** tab is selected
- In the *Commonly used Microsoft APIs* section, click on **Microsoft Graph**
- In the **Delegated permissions** section, ensure that the right permissions are checked: **User.ReadBasic.All**. Use the search box if necessary.
- Select the **Add permissions** button

### Step 3: Configure the sample to use your Azure AD tenant

In the steps below, "ClientID" is the same as "Application ID" or "AppId".

#### Configure the pythonwebapp project

> Note: if you used the setup scripts, the changes below may have been applied for you

1. Open the `app_config.py` file
1. Find the app key `Enter_the_Tenant_Name_Here` and replace the existing value with your Azure AD tenant name.
1. You saved your application secret during the creation of the `python-webapp` app in the Azure portal.
Now you can set the secret in environment variable `CLIENT_SECRET`,
and then adjust `app_config.py` to pick it up.
1. Find the app key `Enter_the_Application_Id_here` and replace the existing value with the application ID (clientId) of the `python-webapp` application copied from the Azure portal.


### Step 4: Run the sample

- You will need to install dependencies using pip as follows:
```Shell
$ pip install -r requirements.txt
```

Run Flask on this project's directory (where `app.py` locates). Note that the host and port values need to match what you've set up in your redirect_uri:

```Shell
$ flask run --host localhost --port 5000
```

## Community Help and Support

Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community.
Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
Make sure that your questions or comments are tagged with [`azure-active-directory` `adal` `msal` `python`].
## Contributing

If you find a bug in the sample, please raise the issue on [GitHub Issues](../../issues).

To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory).

## Contributing

If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md).

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [[email protected]](mailto:[email protected]) with any additional questions or comments.

## More information

For more information, see MSAL.Python's [conceptual documentation]("https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki"):


For more information about web apps scenarios on the Microsoft identity platform see [Scenario: Web app that calls web APIs](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-web-app-call-api-overview)

For more information about how OAuth 2.0 protocols work in this scenario and other scenarios, see [Authentication Scenarios for Azure AD](http://go.microsoft.com/fwlink/?LinkId=394414).
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [[email protected]](mailto:[email protected]) with any additional questions or comments.
Binary file removed ReadmeFiles/topology.png
Binary file not shown.
34 changes: 22 additions & 12 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from flask import Flask, render_template, session, request, redirect, url_for
from flask_session import Session # https://pythonhosted.org/Flask-Session
import identity, identity.web
import identity
import identity.web
import requests
import app_config
from flask import Flask, redirect, render_template, request, session, url_for
from flask_session import Session

import app_config

app = Flask(__name__)
app.config.from_object(app_config)
Expand All @@ -12,7 +13,7 @@
# This section is needed for url_for("foo", _external=True) to automatically
# generate http scheme when this sample is running on localhost,
# and to generate https scheme when it is deployed behind reversed proxy.
# See also https://flask.palletsprojects.com/en/1.0.x/deploying/wsgi-standalone/#proxy-setups
# See also https://flask.palletsprojects.com/en/2.2.x/deploying/proxy_fix/
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)

Expand All @@ -21,41 +22,50 @@
authority=app.config.get("AUTHORITY"),
client_id=app.config["CLIENT_ID"],
client_credential=app.config["CLIENT_SECRET"],
)
)


@app.route("/login")
def login():
return render_template("login.html", version=identity.__version__, **auth.log_in(
scopes=app_config.SCOPE, # Have user consent scopes during log-in
redirect_uri=url_for("auth_response", _external=True), # Optional. If present, this absolute URL must match your app's redirect_uri registered in Azure Portal
scopes=app_config.SCOPE, # Have user consent to scopes during log-in
redirect_uri=url_for("auth_response", _external=True), # Optional. If present, this absolute URL must match your app's redirect_uri registered in Azure Portal
))


@app.route(app_config.REDIRECT_PATH)
def auth_response():
result = auth.complete_log_in(request.args)
return render_template("auth_error.html", result=result) if "error" in result else redirect(url_for("index"))
if "error" in result:
return render_template("auth_error.html", result=result)
return redirect(url_for("index"))


@app.route("/logout")
def logout():
return redirect(auth.log_out(url_for("index", _external=True)))


@app.route("/")
def index():
if not auth.get_user():
return redirect(url_for("login"))
return render_template('index.html', user=auth.get_user(), version=identity.__version__)


@app.route("/call_downstream_api")
def call_downstream_api():
token = auth.get_token_for_user(app_config.SCOPE)
if "error" in token:
return redirect(url_for("login"))
api_result = requests.get( # Use token to call downstream api
# Use access token to call downstream api
api_result = requests.get(
app_config.ENDPOINT,
headers={'Authorization': 'Bearer ' + token['access_token']},
).json()
timeout=30,
).json()
return render_template('display.html', result=api_result)


if __name__ == "__main__":
app.run()

26 changes: 12 additions & 14 deletions app_config.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import os

CLIENT_ID = "Enter_the_Application_Id_here" # Application (client) ID of app registration
# Application (client) ID of app registration
CLIENT_ID = os.getenv("CLIENT_ID")
# Application's generated client secret: never check this into source control!
CLIENT_SECRET = os.getenv("CLIENT_SECRET")

CLIENT_SECRET = "Enter_the_Client_Secret_Here" # Placeholder - for use ONLY during testing.
# In a production app, we recommend you use a more secure method of storing your secret,
# like Azure Key Vault. Or, use an environment variable as described in Flask's documentation:
# https://flask.palletsprojects.com/en/1.1.x/config/#configuring-from-environment-variables
# CLIENT_SECRET = os.getenv("CLIENT_SECRET")
# if not CLIENT_SECRET:
# raise ValueError("Need to define CLIENT_SECRET environment variable")

AUTHORITY = "https://login.microsoftonline.com/common" # For multi-tenant app
# AUTHORITY = "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here"
# AUTHORITY = "https://login.microsoftonline.com/" # For multi-tenant app
AUTHORITY = f"https://login.microsoftonline.com/{os.getenv('TENANT_ID')}"

REDIRECT_PATH = "/getAToken" # Used for forming an absolute URL to your redirect URI.
# The absolute URL must match the redirect URI you set
# in the app's registration in the Azure portal.
# The absolute URL must match the redirect URI you set
# in the app's registration in the Azure portal.

# You can find more Microsoft Graph API endpoints from Graph Explorer
# https://developer.microsoft.com/en-us/graph/graph-explorer
Expand All @@ -25,4 +20,7 @@
# https://docs.microsoft.com/en-us/graph/permissions-reference
SCOPE = ["User.ReadBasic.All"]

SESSION_TYPE = "filesystem" # Specifies the token cache should be stored in server-side session
# Tells the Flask-session2 extension to store sessions in the filesystem
SESSION_TYPE = "filesystem"
# Using the file system will not work in most production systems,
# it's better to use a database-backed session store instead.
Loading