Identity
Identity is an authentication and authorization library optimized for web applications, building upon Microsoft's MSAL Python. It provides high-level APIs for popular frameworks like Flask, Quart, and Django, simplifying integration with Microsoft Identity Platform. The library is actively maintained with frequent updates addressing bug fixes and introducing new features.
Common errors
-
TypeError: your_view_func() missing 1 required keyword-only argument: 'context'
cause After upgrading to `identity` 0.6.0 or later, Django views decorated with `login_required` now expect a `context` keyword argument.fixModify your Django view function signature to accept `context`: `def your_view_func(request, *, context):` -
RuntimeError: redirect_uri is not configured. Please provide it in Auth(redirect_uri=...)
cause The `redirect_uri` parameter was either omitted or incorrectly configured in the `Auth` constructor, which is crucial for the OAuth flow.fixEnsure that the `Auth` constructor includes a valid `redirect_uri` that matches what's configured in your Identity Provider (e.g., Azure AD App Registration): `auth = Auth(..., redirect_uri="http://localhost:5000/redirect")` -
Users are always prompted to log in, even if they have an active session with the Identity Provider.
cause As of `identity` 0.11.0, the default for the `prompt` parameter in the `Auth` constructor changed, allowing automatic sign-in. If you upgrade from an older version, the behavior might seem like it's always prompting.fixIf you explicitly want to force a login prompt every time (the old behavior), set `prompt="select_account"` in your `Auth` configuration: `auth = Auth(..., prompt="select_account")`.
Warnings
- breaking The default behavior of the `prompt` parameter in `Auth` has changed. Previously, it effectively always prompted for login; now, it defaults to `None` which allows automatic sign-in if an active session with the identity provider already exists.
- breaking Django views decorated by `@login_required` must now accept a keyword-only parameter named `context`.
- breaking The Django API's logout URL was normalized, potentially breaking existing logout links in templates.
- gotcha When using Flask/Quart factory patterns, `Auth` must be initialized using `init_app(app)` after the application object is created.
Install
-
pip install identity[flask] -
pip install identity[django] -
pip install identity[quart] -
pip install identity
Imports
- Auth
from identity.web import Auth
from identity.flask import Auth
- login_required
from identity.web import login_required
from identity.flask import login_required
Quickstart
import os
from flask import Flask, render_template_string, session, redirect, url_for, request
from identity.flask import Auth, login_required
app = Flask(__name__)
app.secret_key = os.urandom(32) # Use a strong, rotated key in production
# Configure Identity using environment variables
auth = Auth(
app,
authority=os.environ.get('IDENTITY_AUTHORITY', 'https://login.microsoftonline.com/common'),
client_id=os.environ.get('IDENTITY_CLIENT_ID', 'YOUR_CLIENT_ID'),
client_secret=os.environ.get('IDENTITY_CLIENT_SECRET', 'YOUR_CLIENT_SECRET'),
redirect_uri=os.environ.get('IDENTITY_REDIRECT_URI', 'http://localhost:5000/redirect'),
endpoint=os.environ.get('IDENTITY_ENDPOINT', 'https://graph.microsoft.com/v1.0/users'),
scope=os.environ.get('IDENTITY_SCOPE', 'User.ReadBasic.All').split()
)
@app.route("/")
@login_required
def index():
user_data = session.get('user', {})
return render_template_string(
"""
<h1>Welcome, {{ user.get('name', 'Guest') }}!</h1>
<p>Logged in user details: {{ user }}</p>
<p><a href="{{ url_for('logout') }}">Logout</a></p>
""",
user=user_data
)
@app.route(auth.redirect_uri_path)
def auth_redirect():
auth.complete_login(request.args)
return redirect(url_for("index"))
@app.route("/logout")
def logout():
return auth.logout(url_for("index", _external=True))
if __name__ == "__main__":
# Set dummy values for quick local test if env vars are not set
os.environ.setdefault('IDENTITY_CLIENT_ID', 'YOUR_CLIENT_ID_FROM_AZURE')
os.environ.setdefault('IDENTITY_CLIENT_SECRET', 'YOUR_CLIENT_SECRET_FROM_AZURE')
# Make sure to replace YOUR_TENANT_ID with your actual tenant ID or 'common' for multi-tenant
os.environ.setdefault('IDENTITY_AUTHORITY', 'https://login.microsoftonline.com/YOUR_TENANT_ID')
os.environ.setdefault('IDENTITY_REDIRECT_URI', 'http://localhost:5000/redirect')
os.environ.setdefault('IDENTITY_ENDPOINT', 'https://graph.microsoft.com/v1.0/me')
os.environ.setdefault('IDENTITY_SCOPE', 'User.ReadBasic.All')
print("\n--- To run this app, make sure to replace placeholders YOUR_CLIENT_ID_FROM_AZURE and YOUR_CLIENT_SECRET_FROM_AZURE with actual values from your Azure App Registration. ---\n")
app.run(debug=True, port=5000)