1- import uuid
21import requests
32from flask import Flask , render_template , session , request , redirect , url_for
43from flask_session import Session # https://pythonhosted.org/Flask-Session
5- import msal
4+ from werkzeug .exceptions import Unauthorized , Forbidden
5+ from identity import __version__
6+ from identity .web import Web , LifespanValidator
67import app_config
78
89
1718from werkzeug .middleware .proxy_fix import ProxyFix
1819app .wsgi_app = ProxyFix (app .wsgi_app , x_proto = 1 , x_host = 1 )
1920
21+ web = Web (
22+ session = session ,
23+ authority = app .config .get ("AUTHORITY" ),
24+ client_id = app .config ["CLIENT_ID" ],
25+ client_credential = app .config ["CLIENT_SECRET" ],
26+ redirect_uri = "http://localhost:5000" + app .config ["REDIRECT_PATH" ], # It must match your redirect_uri
27+ validators = [LifespanValidator (seconds = 3600 , on_error = Unauthorized ("Login expired" ))],
28+ )
29+
2030@app .route ("/" )
2131def index ():
22- if not session . get ( "user" ):
32+ if not web . get_user ( ):
2333 return redirect (url_for ("login" ))
24- return render_template ('index.html' , user = session [ "user" ] , version = msal . __version__ )
34+ return render_template ('index.html' , user = web . get_user () , version = __version__ )
2535
2636@app .route ("/login" )
2737def 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__ )
38+ return render_template ("login.html" , version = __version__ , ** web .start_auth (scopes = app_config .SCOPE ))
39+
40+ @app .errorhandler (Unauthorized )
41+ def handler (error ):
42+ return redirect (url_for ("login" ))
3243
3344@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+ def auth_response ():
46+ result = web .complete_auth (request .args )
47+ if "error" in result :
48+ return render_template ("auth_error.html" , result = result )
4549 return redirect (url_for ("index" ))
4650
4751@app .route ("/logout" )
4852def 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 ))
53+ return redirect (web .sign_out (url_for ("index" , _external = True )))
5354
5455@app .route ("/graphcall" )
5556def graphcall ():
56- token = _get_token_from_cache (app_config .SCOPE )
57+ token = web . get_token (app_config .SCOPE )
5758 if not token :
5859 return redirect (url_for ("login" ))
5960 graph_data = requests .get ( # Use token to call downstream service
@@ -62,38 +63,6 @@ def graphcall():
6263 ).json ()
6364 return render_template ('display.html' , result = graph_data )
6465
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
96-
9766if __name__ == "__main__" :
9867 app .run ()
9968
0 commit comments