Skip to content

Commit 9672145

Browse files
authored
Merge pull request #88 from Azure-Samples/identity-web
Use a new helper library Identity Web
2 parents 0cc2f1e + d13d691 commit 9672145

File tree

7 files changed

+52
-80
lines changed

7 files changed

+52
-80
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ see [Authentication Scenarios for Azure AD](https://docs.microsoft.com/en-us/azu
3535

3636
To run this sample, you'll need:
3737

38-
> - [Python 2.7+](https://www.python.org/downloads/release/python-2713/) or [Python 3+](https://www.python.org/downloads/release/python-364/)
38+
> - [Python 3](https://www.python.org/downloads/)
3939
> - 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)
4040
4141

@@ -47,7 +47,7 @@ From your shell or command line:
4747
git clone https://github.com/Azure-Samples/ms-identity-python-webapp.git
4848
```
4949

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

5252
> 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.
5353
@@ -138,7 +138,7 @@ In the steps below, "ClientID" is the same as "Application ID" or "AppId".
138138
$ pip install -r requirements.txt
139139
```
140140

141-
Run app.py from shell or command line. Note that the host and port values need to match what you've set up in your redirect_uri:
141+
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:
142142

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

app.py

Lines changed: 29 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import uuid
2-
import requests
31
from flask import Flask, render_template, session, request, redirect, url_for
42
from flask_session import Session # https://pythonhosted.org/Flask-Session
5-
import msal
3+
import identity, identity.web
4+
import requests
65
import app_config
76

87

@@ -17,82 +16,45 @@
1716
from werkzeug.middleware.proxy_fix import ProxyFix
1817
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
1918

20-
@app.route("/")
21-
def index():
22-
if not session.get("user"):
23-
return redirect(url_for("login"))
24-
return render_template('index.html', user=session["user"], version=msal.__version__)
19+
auth = identity.web.Auth(
20+
session=session,
21+
authority=app.config.get("AUTHORITY"),
22+
client_id=app.config["CLIENT_ID"],
23+
client_credential=app.config["CLIENT_SECRET"],
24+
)
2525

2626
@app.route("/login")
2727
def login():
28-
# Technically we could use empty list [] as scopes to do just sign in,
29-
# here we choose to also collect end user consent upfront
30-
session["flow"] = _build_auth_code_flow(scopes=app_config.SCOPE)
31-
return render_template("login.html", auth_url=session["flow"]["auth_uri"], version=msal.__version__)
28+
return render_template("login.html", version=identity.__version__, **auth.log_in(
29+
scopes=app_config.SCOPE, # Have user consent scopes during log-in
30+
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
31+
))
3232

33-
@app.route(app_config.REDIRECT_PATH) # Its absolute URL must match your app's redirect_uri set in AAD
34-
def authorized():
35-
try:
36-
cache = _load_cache()
37-
result = _build_msal_app(cache=cache).acquire_token_by_auth_code_flow(
38-
session.get("flow", {}), request.args)
39-
if "error" in result:
40-
return render_template("auth_error.html", result=result)
41-
session["user"] = result.get("id_token_claims")
42-
_save_cache(cache)
43-
except ValueError: # Usually caused by CSRF
44-
pass # Simply ignore them
45-
return redirect(url_for("index"))
33+
@app.route(app_config.REDIRECT_PATH)
34+
def auth_response():
35+
result = auth.complete_log_in(request.args)
36+
return render_template("auth_error.html", result=result) if "error" in result else redirect(url_for("index"))
4637

4738
@app.route("/logout")
4839
def logout():
49-
session.clear() # Wipe out user and its token cache from session
50-
return redirect( # Also logout from your tenant's web session
51-
app_config.AUTHORITY + "/oauth2/v2.0/logout" +
52-
"?post_logout_redirect_uri=" + url_for("index", _external=True))
40+
return redirect(auth.log_out(url_for("index", _external=True)))
41+
42+
@app.route("/")
43+
def index():
44+
if not auth.get_user():
45+
return redirect(url_for("login"))
46+
return render_template('index.html', user=auth.get_user(), version=identity.__version__)
5347

54-
@app.route("/graphcall")
55-
def graphcall():
56-
token = _get_token_from_cache(app_config.SCOPE)
57-
if not token:
48+
@app.route("/call_downstream_api")
49+
def call_downstream_api():
50+
token = auth.get_token_for_user(app_config.SCOPE)
51+
if "error" in token:
5852
return redirect(url_for("login"))
59-
graph_data = requests.get( # Use token to call downstream service
53+
api_result = requests.get( # Use token to call downstream api
6054
app_config.ENDPOINT,
6155
headers={'Authorization': 'Bearer ' + token['access_token']},
6256
).json()
63-
return render_template('display.html', result=graph_data)
64-
65-
66-
def _load_cache():
67-
cache = msal.SerializableTokenCache()
68-
if session.get("token_cache"):
69-
cache.deserialize(session["token_cache"])
70-
return cache
71-
72-
def _save_cache(cache):
73-
if cache.has_state_changed:
74-
session["token_cache"] = cache.serialize()
75-
76-
def _build_msal_app(cache=None, authority=None):
77-
return msal.ConfidentialClientApplication(
78-
app_config.CLIENT_ID, authority=authority or app_config.AUTHORITY,
79-
client_credential=app_config.CLIENT_SECRET, token_cache=cache)
80-
81-
def _build_auth_code_flow(authority=None, scopes=None):
82-
return _build_msal_app(authority=authority).initiate_auth_code_flow(
83-
scopes or [],
84-
redirect_uri=url_for("authorized", _external=True))
85-
86-
def _get_token_from_cache(scope=None):
87-
cache = _load_cache() # This web app maintains one cache per session
88-
cca = _build_msal_app(cache=cache)
89-
accounts = cca.get_accounts()
90-
if accounts: # So all account(s) belong to the current signed-in user
91-
result = cca.acquire_token_silent(scope, account=accounts[0])
92-
_save_cache(cache)
93-
return result
94-
95-
app.jinja_env.globals.update(_build_auth_code_flow=_build_auth_code_flow) # Used in template
57+
return render_template('display.html', result=api_result)
9658

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

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ werkzeug>=2
1212

1313
flask-session>=0.3.2,<0.5
1414
requests>=2,<3
15-
msal>=1.7,<2
15+
identity>=0.2,<0.3
1616

1717
# cachelib==0.1 # Only need this if you are running Python 2
1818
# Note: This sample does NOT directly depend on cachelib.

templates/auth_error.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
<head>
44
<meta charset="UTF-8">
55

6-
{% if config.get("B2C_RESET_PASSWORD_AUTHORITY") and "AADB2C90118" in result.get("error_description") %}
6+
{% if config.get("B2C_RESET_PASSWORD_AUTHORITY") and "AADB2C90118" in result.get("error_description") %} <!-- This will be reached when user forgot their password -->
77
<!-- See also https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-policies#linking-user-flows -->
8-
<meta http-equiv="refresh" content='0;{{_build_auth_code_flow(authority=config["B2C_RESET_PASSWORD_AUTHORITY"])["auth_uri"]}}'>
8+
<meta http-equiv="refresh" content='0;{{config.get("B2C_RESET_PASSWORD_AUTHORITY")}}?client_id={{config.get("CLIENT_ID")}}'>
99
{% endif %}
1010
</head>
1111
<body>

templates/display.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</head>
66
<body>
77
<a href="javascript:window.history.go(-1)">Back</a> <!-- Displayed on top of a potentially large JSON response, so it will remain visible -->
8-
<h1>Graph API Call Result</h1>
8+
<h1>Result of the downstream API Call</h1>
99
<pre>{{ result |tojson(indent=4) }}</pre> <!-- Just a generic json viewer -->
1010
</body>
1111
</html>

templates/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ <h1>Microsoft Identity Python Web App</h1>
88
<h2>Welcome {{ user.get("name") }}!</h2>
99

1010
{% if config.get("ENDPOINT") %}
11-
<li><a href='/graphcall'>Call Microsoft Graph API</a></li>
11+
<li><a href='/call_downstream_api'>Call a downstream API</a></li>
1212
{% endif %}
1313

1414
{% if config.get("B2C_PROFILE_AUTHORITY") %}
15-
<li><a href='{{_build_auth_code_flow(authority=config["B2C_PROFILE_AUTHORITY"])["auth_uri"]}}'>Edit Profile</a></li>
15+
<li><a href='{{config.get("B2C_PROFILE_AUTHORITY")}}?client_id={{config.get("CLIENT_ID")}}'>Edit Profile</a></li>
1616
{% endif %}
1717

1818
<li><a href="/logout">Logout</a></li>
1919
<hr>
20-
<footer style="text-align: right">Powered by MSAL Python {{ version }}</footer>
20+
<footer style="text-align: right">Powered by Identity Web {{ version }}</footer>
2121
</body>
2222
</html>
2323

templates/login.html

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@
66
<body>
77
<h1>Microsoft Identity Python Web App</h1>
88

9-
<li><a href='{{ auth_url }}'>Sign In</a></li>
9+
{% if user_code %}
10+
<ol>
11+
<li>To sign in, type <b>{{ user_code }}</b> into
12+
<a href='{{ auth_uri }}' target=_blank>{{ auth_uri }}</a>
13+
to authenticate.
14+
</li>
15+
<li>And then <a href="{{ url_for('auth_response') }}">proceed</a>.</li>
16+
</ol>
17+
{% else %}
18+
<li><a href='{{ auth_uri }}'>Sign In</a></li>
19+
{% endif %}
1020

1121
{% if config.get("B2C_RESET_PASSWORD_AUTHORITY") %}
12-
<li><a href='{{_build_auth_code_flow(authority=config["B2C_RESET_PASSWORD_AUTHORITY"])["auth_uri"]}}'>Reset Password</a></li>
22+
<li><a href="{{config.get('B2C_RESET_PASSWORD_AUTHORITY')}}?client_id={{config.get('CLIENT_ID')}}">Reset Password</a></li>
1323
{% endif %}
1424

1525
<hr>
16-
<footer style="text-align: right">Powered by MSAL Python {{ version }}</footer>
26+
<footer style="text-align: right">Powered by Identity Web {{ version }}</footer>
1727
</body>
1828
</html>
1929

0 commit comments

Comments
 (0)