This is the first post in a series of posts about creating a web app to manage my social media. The main page for this series is here.
We begin by creating a new App Service in Azure using the Azure Portal.
I did not use the sample code in section 1 from the link above, because I intended to use the code that uses the Microsoft Authentication Library (MSAL) for Python. I just created an empty app.
From the defaults, I changed the App plan to a cheaper plan. The free plan (F1) also works. I also configured the app to use the Python 3.11 runtime, which is the version I’m using to develop.
I then configured the app to sync with GitHub for continuous deployment, so that the app automatically updates in production when I push changes to GitHub. I simply followed the instructions on the link, which are very straightfoward: I selected GitHub as the source, authorized Azure to access my GitHub account, selected the repository and branch, and then selected the option to deploy the code automatically when I push changes to GitHub.
Configuring Microsoft Entra ID and Linking the App
Ultimately, I want to ensure that users are only allowed to see the app if they are authorized. To do this, I will use Microsoft Entra ID to authenticate users. To get to that, we first need to authenticate the users, and then authorize them to access the app. This post is about the authentication part.
Authentication is the process of verifying a user’s identity, typically using credentials like usernames and passwords. Authorization, on the other hand, determines if an authenticated user has permissions to access certain resources or perform specific actions. While authentication confirms who the user is, authorization decides what an authenticated user is allowed to do. Authentication is the first step, necessary before authorization can be applied to manage access and permissions.
To create an app that does authentication, I followed the instructions at Quickstart: Sign in users and call the Microsoft Graph API from a Python web app. The instructions will tell you where to find the CLIENT_ID
and how to generate a CLIENT_SECRET
for the app. The AUTHORITY
needs to be set to AUTHORITY="https://login.microsoftonline.com/<tenant_id>"
, which I obtained from the Azure Portal, in the Overview page of Microsoft Entra ID.
I downloaded the full code for the app, and the only change I had to make was to create an .env
file with the variables described above. This is not required for the app to run on the web, but it helps when running locally.
The .env
file should look like this:
CLIENT_ID=[client_id]
CLIENT_SECRET=[client_secret]
AUTHORITY=https://login.microsoftonline.com/[tenant_id]
The instructions don’t mention this, but after finishing the instructions, you also have to link the Entra ID configuration to the Web App you created in the first step. To do this, go to the Azure Portal, open the App Service, go to the “Authentication” tab, and click on “Microsoft” under “Identity providers”. Then, select the “Existing App” tab and select the app you created in the previous step.
The final connection step was to set up the environment variables CLIENT_ID
, CLIENT_SECRET
and AUTHORITY
in the App Service. I followed the instructions on this link to set the environment variables in the Web App to the same values present in the .env file.
Once I did all this, I pushed my code to GitHub and it was automatically deployed to the App Service.
Code walkthrough
The code is for a Flask Web App. Flask is a Python framework for building web apps. I decided to use it instead of other alternatives like Angular or NextJS because it allows me to use Python, which is the language I’m most familiar with. The code is in the app.py
file, and I will walk through it below. I also created an app_config.py
file to store the configuration variables, which you can see in GitHub here. I will not walk through the app_config.py
file, because it is just a file to store the configuration variables.
The app.py file
The first thing we do is import the libraries we will use. The identity.web
library is the one that does the authentication. The requests
library is used to make HTTP requests. The Flask
library is the framework we are using to build the web app. The redirect
, render_template
, request
, session
, and url_for
libraries are all from Flask. The Session
library is used to store the session information. The app_config
library is the one we created to store the configuration variables.
import identity.web
import requests
from flask import Flask, redirect, render_template, request, session, url_for
from flask_session import Session
import app_config
= "0.7.0" # The version of this sample, for troubleshooting purpose
__version__
= Flask(__name__)
app
app.config.from_object(app_config)assert app.config["REDIRECT_PATH"] != "/", "REDIRECT_PATH must not be /"
The next thing we do is create a session. This is used to store the session information. The Session
library is used to store the session information. The app.jinja_env.globals.update(Auth=identity.web.Auth)
is not used in my code.
Session(app)
# 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/2.2.x/deploying/proxy_fix/
from werkzeug.middleware.proxy_fix import ProxyFix
= ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
app.wsgi_app
globals.update(Auth=identity.web.Auth) # Useful in template for B2C
app.jinja_env.= identity.web.Auth(
auth =session,
session=app.config["AUTHORITY"],
authority=app.config["CLIENT_ID"],
client_id=app.config["CLIENT_SECRET"],
client_credential )
The next thing we do is create a route for the login page. The @app.route("/login")
is a decorator that tells Flask that the function below it is a route for the login page. The render_template
function renders the login.html
template, which is the login page.
The important line in the login template is line 19, which initiates the login process by directing the user to the auth_response
route, which is the same as app_config.REDIRECT_PATH
.
The auth.log_in
function comes from the identity.web
library, and returns a dictionary with the information needed to log in. The scopes
parameter is used to tell the user what the app will be able to do on their behalf (in my case, just read their basic information). The redirect_uri
parameter is optional, must match the redirect URI registered in the Azure Portal. The prompt
parameter is also optional, and it can take different values, which are defined in this link.
If the authentication is successful, the auth_response
route will be called. If the authentication is not successful, the auth_error.html
template will be rendered.
@app.route("/login")
def login():
return render_template("login.html", version=__version__, **auth.log_in(
=app_config.SCOPE, # Have user consent to scopes during log-in
scopes=url_for("auth_response", _external=True),
redirect_uri="select_account",
prompt ))
The next thing we do is create a route for the auth_response
page. The auth.complete_log_in(request.args)
function is from the identity.web
library, and it returns a dictionary with the result of the log in process, which can be successful or an error, in which case it will contain an error message.
@app.route(app_config.REDIRECT_PATH)
def auth_response():
= auth.complete_log_in(request.args)
result if "error" in result:
return render_template("auth_error.html", result=result)
return redirect(url_for("index"))
If the login was successful, we show the index page, which lists the information we collected about the user from the authentication provider.
I am only using Microsoft Accounts (formerly Live, currently used in Xbox and Outlook.com) as the authentication provider for now. I may add Google Accounts later, but I don’t intend to manage my own authentication, even though the code supports it.
I call the function auth.get_user()
from the identity.web
library, and it returns a dictionary with the information about the user. The render_template
function renders the index.html
template, which is the index page. The user
parameter is used to pass the user information to the template. The index page will display the user name and the subscription ID.
@app.route("/")
def index():
if not auth.get_user():
return redirect(url_for("login"))
return render_template('index.html', user=auth.get_user(), version=__version__)
if __name__ == "__main__":
app.run()
Testing
I navigated to the app URL and was able to sign in with my Microsoft account. I was then redirected to the app, where I could see the information about my login.
Now that I have an app that can authenticate users, I need to work on authorization, so that only authorized users can access the app.