Python OAuth2 Azure AD Integration – Complete Guide
This tutorial walks you through registering an Azure AD app, using MSAL in a Flask project, and securely obtaining tokens to call Microsoft Graph.
Focus: python oauth2 azure ad
OAuth 2.0 and OpenID Connect with Microsoft Entra (Azure AD) – Python Integration Guide
Overview
OAuth 2.0 is the industry standard for delegated authorization. OpenID Connect (OIDC) builds on OAuth 2.0 to provide authentication – it returns an ID token that proves the user’s identity. Microsoft Entra (formerly Azure AD) implements both protocols and offers a simple way for Python applications to sign‑in users, obtain tokens, and call protected APIs such as Microsoft Graph.
This tutorial walks you through a complete, practical integration:
- Register an app in Azure AD.
- Install the required Python packages.
- Implement the OAuth 2.0 Authorization Code flow with MSAL.
- Retrieve and verify the OpenID Connect ID token.
- Call a protected API (Microsoft Graph) using the access token.
All code examples are ready to copy‑paste into a small Flask project.
Prerequisites
- Python 3.8 or newer installed.
- pip package manager.
- An Azure account with permission to create an App Registration in Microsoft Entra.
- Basic familiarity with HTTP requests and JSON.
Register an Application in Azure AD
Create a new app registration
- Sign in to the Azure portal → Azure Active Directory → App registrations → New registration.
- Give the app a name (e.g., python‑oauth‑demo).
- Choose Supported account types – for a demo, select Accounts in this organizational directory only.
- After creation, note the Application (client) ID and Directory (tenant) ID.
- Under Certificates & secrets, click New client secret, add a description, set an expiration, and copy the generated secret value. Keep it safe – you’ll need it in code.
Configure Redirect URI
For a web app that runs locally, add a redirect URI such as:
http://localhost:5000/get_tokenIf you later deploy to a public URL, replace it with that address.
Set API permissions
- Go to API permissions → Add a permission → Microsoft Graph.
- Choose Delegated permissions and add at least
User.Read(for basic profile) andopenid profile email(required for OIDC). - Click Grant admin consent (requires admin rights) or have a user consent at sign‑in time.
Install Required Python Packages
pip install msal requests pyjwt- msal – Microsoft Authentication Library for Python (handles token acquisition).
- requests – simple HTTP client for calling Microsoft Graph.
- pyjwt – verify the JWT signature of the ID token.
Implement OAuth 2.0 Authorization Code Flow
Step 1: Build the Authorization URL
The user is redirected to Azure AD’s endpoint. The URL must contain:
client_id– your app’s client ID.response_type=code– indicates we want an authorization code.redirect_uri– must match the one set in the portal.scope– at minimumopenid profile email.state– optional but recommended to mitigate CSRF.
import urllib.parse
CLIENT_ID = "YOUR_CLIENT_ID"
TENANT_ID = "YOUR_TENANT_ID"
REDIRECT_URI = "http://localhost:5000/get_token"
SCOPES = ["openid", "profile", "email"]
def build_auth_url():
params = {
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"scope": " ".join(SCOPES),
"state": "random_state_string", # generate securely in real code
"response_mode": "query"
}
return f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize?" + urllib.parse.urlencode(params)Print or open this URL in a browser to start the sign‑in process.
Step 2: Handle the Callback
A minimal Flask app receives the code query parameter after the user consents.
from flask import Flask, request, redirect, session, jsonify
import msal
app = Flask(__name__)
app.secret_key = "replace_with_strong_secret"
# MSAL configuration
authority = f"https://login.microsoftonline.com/{TENANT_ID}"
app_config = {
"client_id": CLIENT_ID,
"client_secret": "YOUR_CLIENT_SECRET", # keep this safe
"authority": authority,
"redirect_uri": REDIRECT_URI,
}
msal_app = msal.PublicClientApplication(app_config)
@app.route("/")
def index():
# Generate a link and send the user to Azure AD
auth_url = build_auth_url()
return f'<a href="{auth_url}">Sign in with Microsoft</a>'
@app.route("/get_token")
def get_token():
# Azure AD redirects here with ?code=...&state=...
error = request.args.get("error")
if error:
return f"Error: {error}", 400
code = request.args.get("code")
if not code:
return "Authorization code missing", 400
# Exchange the code for tokens
result = msal_app.acquire_token_by_authorization_code(
code=code,
scopes=SCOPES,
redirect_uri=REDIRECT_URI,
)
if "access_token" in result:
session["access_token"] = result["access_token"]
session["id_token"] = result.get("id_token")
return redirect("/profile")
else:
return f"Token acquisition error: {result.get('error_description')}", 400Step 3: Use the Access Token to Call Microsoft Graph
With the access token stored in the session, we can call /me to fetch the signed‑in user’s profile.
import requests
@app.route("/profile")
def profile():
access_token = session.get("access_token")
if not access_token:
return redirect("/")
headers = {"Authorization": f"Bearer {access_token}"}
graph_url = "https://graph.microsoft.com/v1.0/me"
response = requests.get(graph_url, headers=headers)
if response.status_code == 200:
return jsonify(response.json())
else:
return f"Graph request failed: {response.text}", 500Run the Flask app (flask run) and navigate to http://localhost:5000. Click Sign in with Microsoft, complete the consent screen, and you’ll be redirected back to /profile with a JSON payload containing the user’s name, email, and object ID.
OpenID Connect – Adding ID Token Verification
The ID token is a JWT that asserts the user’s identity. It contains claims such as sub (subject), iss (issuer), aud (audience), and exp (expiration). Verifying these claims ensures the token is genuine.
Install pyjwt
pip install pyjwtVerify the ID token
import jwt
import json
def verify_id_token(id_token):
# MSAL already validated the signature, but we double‑check the claims
unverified_header = jwt.get_unverified_header(id_token)
kid = unverified_header["kid"]
# In a production app you would fetch the JWKS endpoint and cache the keys.
# For this demo we use the built‑in Microsoft public keys endpoint.
jwks_url = f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys"
jwks = requests.get(jwks_url).json()
# Find the key that matches the kid
key = None
for key_dict in jwks["values"]:
if key_dict["kid"] == unverified_header["kid"]:
key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))
break
if not key:
raise ValueError("Unable to find matching signing key")
# Verify claims
claims = jwt.decode(
id_token,
key=key,
algorithms=[unverified_header["alg"]],
audience=CLIENT_ID,
issuer=f"https://login.microsoftonline.com/{TENANT_ID}/v2.0",
)
return claims
# Example usage after token acquisition
@app.route("/profile")
def profile():
# ... (same as before) ...
id_token = session.get("id_token")
if id_token:
try:
claims = verify_id_token(id_token)
return f"Signed in as {claims['name']} (Object ID: {claims['sub']})"
except Exception as e:
return f"ID token verification failed: {e}", 400
return "No ID token in session"The verify_id_token function:
- Retrieves Microsoft’s public JWKS (JSON Web Key Set) to obtain the signing key.
- Validates the token’s
aud(must match the client ID) andiss(must be the expected issuer). - Returns the decoded claims, which you can safely display or store.
Protecting a Web API with Azure AD (Optional)
If you want your own Flask API to accept only Azure AD‑authenticated calls, you can add the Authorization: Bearer <token> header check:
from functools import wraps
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get("Authorization")
if not token or not token.startswith("Bearer "):
return jsonify({"error": "Missing or malformed token"}), 401
try:
# Use msal to validate the token (or pyjwt as above)
claims = verify_id_token(token.split()[1])
request.user = claims # make user info available downstream
except Exception as e:
return jsonify({"error": str(e)}), 401
return f(*args, **kwargs)
return decorated
@app.route("/secure-data")
@require_auth
def secure_data():
return jsonify({"message": f"Hello {request.user['name']}, this is protected data."})Now any client that obtains a token from Azure AD can call /secure-data and receive a 401 if the token is missing or invalid.
Common Errors and Debugging
- Invalid redirect_uri – Ensure the URI in the portal exactly matches the one used in the code (including scheme and port).
- Authorization code not found – The code expires after a few minutes; retry quickly or increase the timeout in your app.
- Insufficient scopes – If you request
openidbut omitprofileoremail, the ID token may be missing claims. - Token acquisition errors – Check
result.get('error_description')for details (e.g., consent required, client secret expired). - JWT verification failures – Verify that the
audclaim equals your client ID and that theissmatches the Azure AD tenant URL.
Summary
- Register an app in Microsoft Entra, configure redirect URIs, and grant API permissions.
- Use MSAL to implement the OAuth 2.0 Authorization Code flow in Python (Flask example).
- Retrieve both access token and ID token; verify the ID token with pyjwt and Microsoft’s JWKS endpoint to ensure OIDC compliance.
- Call protected resources (e.g., Microsoft Graph) with the access token.
- Handle common pitfalls and secure your API with token validation.
With these steps, junior developers can quickly build secure, real‑world Python applications that leverage Azure AD for authentication and authorization.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.