From 37631fc1ec82b25959eef6a57e25e1172b3228fd Mon Sep 17 00:00:00 2001 From: Simon Young Date: Thu, 19 Sep 2024 09:37:40 -0700 Subject: [PATCH 1/2] ENG-3631: added azure auth --- azure-auth/azure_auth/.gitignore | 4 ++ azure-auth/azure_auth/auth/core.py | 68 +++++++++++++++++++++++++ azure-auth/azure_auth/azure_auth.py | 15 ++++++ azure-auth/azure_auth/pages/__init__.py | 25 +++++++++ azure-auth/requirements.txt | 2 + azure-auth/rxconfig.py | 5 ++ 6 files changed, 119 insertions(+) create mode 100644 azure-auth/azure_auth/.gitignore create mode 100644 azure-auth/azure_auth/auth/core.py create mode 100644 azure-auth/azure_auth/azure_auth.py create mode 100644 azure-auth/azure_auth/pages/__init__.py create mode 100644 azure-auth/requirements.txt create mode 100644 azure-auth/rxconfig.py diff --git a/azure-auth/azure_auth/.gitignore b/azure-auth/azure_auth/.gitignore new file mode 100644 index 00000000..e97bb370 --- /dev/null +++ b/azure-auth/azure_auth/.gitignore @@ -0,0 +1,4 @@ +*.db +*.py[cod] +.web +__pycache__/ diff --git a/azure-auth/azure_auth/auth/core.py b/azure-auth/azure_auth/auth/core.py new file mode 100644 index 00000000..4022c7f1 --- /dev/null +++ b/azure-auth/azure_auth/auth/core.py @@ -0,0 +1,68 @@ +from urllib.parse import parse_qs, urlparse +import msal +import reflex as rx +from typing import Dict + +client_id: str = "0df2a88e-fddb-4cc2-b3e0-f475f162b373" +client_secret: str = "" +tenant_id: str = "f2c9cbbe-006b-46b8-9ad0-d877d8446d6d" +redirect_uri: str = "http://localhost:3000/callback" +authority = f"https://login.microsoftonline.com/{tenant_id}" +login_redirect = "/home" +cache = msal.TokenCache() + + +sso_app: msal.ClientApplication = ( + msal.ConfidentialClientApplication + if client_secret + else msal.PublicClientApplication +)( + client_id=client_id, + client_credential=client_secret, + authority=authority, + token_cache=cache, +) + + +class State(rx.State): + token: Dict[str, str] = {} + access_token: str = " " + flow: dict + + def redirect_sso(self, scope=[]) -> rx.Component: + self.flow = sso_app.initiate_auth_code_flow( + scopes=scope, redirect_uri=redirect_uri + ) + return rx.redirect(self.flow["auth_uri"]) + + def require_auth(self): + if not self.token: + rx.input() + return self.redirect_sso() + + @rx.var + def check_auth(self): + return True if self.token else False + + def logout(self): + self.token = {} + return rx.redirect(authority + "/oauth2/v2.0/logout") + + def callback(self): + query_components = parse_qs(urlparse(self.router.page.raw_path).query) + + auth_response = { + "code": query_components["code"][0], + "client_info": query_components["client_info"][0], + "state": query_components["state"][0], + "session_state": query_components["session_state"][0], + "client-secret": client_secret, + } + result = sso_app.acquire_token_by_auth_code_flow( + self.flow, auth_response, scopes=[] + ) + self.access_token = result[ + "access_token" + ] # this can be used for accessing graph + self.token = result["id_token_claims"] + return rx.redirect(login_redirect) diff --git a/azure-auth/azure_auth/azure_auth.py b/azure-auth/azure_auth/azure_auth.py new file mode 100644 index 00000000..433a513f --- /dev/null +++ b/azure-auth/azure_auth/azure_auth.py @@ -0,0 +1,15 @@ +"""Welcome to Reflex! This file outlines the steps to create a basic app.""" + +import reflex as rx + +from rxconfig import config +from azure_auth.pages import callback, home, logout + + +class State(rx.State): + """The app state.""" + + ... + + +app = rx.App() diff --git a/azure-auth/azure_auth/pages/__init__.py b/azure-auth/azure_auth/pages/__init__.py new file mode 100644 index 00000000..d298423f --- /dev/null +++ b/azure-auth/azure_auth/pages/__init__.py @@ -0,0 +1,25 @@ +import reflex as rx +from azure_auth.auth.core import State as SsoState + + +@rx.page(route="/callback", on_load=SsoState.callback) +def callback() -> rx.Component: + return rx.container() + + +@rx.page(route="/logout", on_load=SsoState.logout) +def logout() -> rx.Component: + return rx.container("Logged out") + + +@rx.page(route="/home", on_load=SsoState.require_auth) +def home() -> rx.Component: + return rx.container(rx.cond(SsoState.check_auth, auth_view(), unauth_view())) + + +def auth_view() -> rx.Component: + return rx.text(f"Hello {SsoState.token['name']}") + + +def unauth_view() -> rx.Component: + return rx.text("Unauthorized, redirected...") diff --git a/azure-auth/requirements.txt b/azure-auth/requirements.txt new file mode 100644 index 00000000..f0e7d445 --- /dev/null +++ b/azure-auth/requirements.txt @@ -0,0 +1,2 @@ +reflex==0.5.10 +msal==1.31.0 diff --git a/azure-auth/rxconfig.py b/azure-auth/rxconfig.py new file mode 100644 index 00000000..ef46c421 --- /dev/null +++ b/azure-auth/rxconfig.py @@ -0,0 +1,5 @@ +import reflex as rx + +config = rx.Config( + app_name="azure_auth", +) From 70546d46102cb006038dee22c3f9f04349743c33 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Sep 2024 16:40:30 -0700 Subject: [PATCH 2/2] ENG:3631: addressing comments --- azure-auth/.gitignore | 5 ++ azure-auth/azure_auth/auth/core.py | 88 ++++++++++++++----------- azure-auth/azure_auth/pages/__init__.py | 7 +- 3 files changed, 58 insertions(+), 42 deletions(-) create mode 100644 azure-auth/.gitignore diff --git a/azure-auth/.gitignore b/azure-auth/.gitignore new file mode 100644 index 00000000..139ed98a --- /dev/null +++ b/azure-auth/.gitignore @@ -0,0 +1,5 @@ +*.db +*.py[cod] +.web +__pycache__/ +assets/external/ diff --git a/azure-auth/azure_auth/auth/core.py b/azure-auth/azure_auth/auth/core.py index 4022c7f1..3b34e862 100644 --- a/azure-auth/azure_auth/auth/core.py +++ b/azure-auth/azure_auth/auth/core.py @@ -1,68 +1,76 @@ -from urllib.parse import parse_qs, urlparse import msal import reflex as rx -from typing import Dict +from typing import Dict, List, Optional client_id: str = "0df2a88e-fddb-4cc2-b3e0-f475f162b373" client_secret: str = "" tenant_id: str = "f2c9cbbe-006b-46b8-9ad0-d877d8446d6d" -redirect_uri: str = "http://localhost:3000/callback" authority = f"https://login.microsoftonline.com/{tenant_id}" -login_redirect = "/home" +login_redirect = "/" cache = msal.TokenCache() - -sso_app: msal.ClientApplication = ( - msal.ConfidentialClientApplication - if client_secret - else msal.PublicClientApplication -)( - client_id=client_id, - client_credential=client_secret, - authority=authority, - token_cache=cache, -) +sso_app: msal.ClientApplication +if client_secret: + sso_app = msal.ConfidentialClientApplication( + client_id=client_id, + client_credential=client_secret, + authority=authority, + token_cache=cache, + ) +else: + sso_app = msal.PublicClientApplication( + client_id=client_id, + authority=authority, + token_cache=cache, + ) class State(rx.State): - token: Dict[str, str] = {} - access_token: str = " " - flow: dict + _access_token: str = "" + _flow: dict + _token: Dict[str, str] = {} - def redirect_sso(self, scope=[]) -> rx.Component: - self.flow = sso_app.initiate_auth_code_flow( - scopes=scope, redirect_uri=redirect_uri + def redirect_sso(self, scope: Optional[List] = None) -> rx.Component: + if scope is None: + scope = [] + self._flow = sso_app.initiate_auth_code_flow( + scopes=scope, redirect_uri=f"{self.router.page.host}/callback" ) - return rx.redirect(self.flow["auth_uri"]) + return rx.redirect(self._flow["auth_uri"]) def require_auth(self): - if not self.token: - rx.input() + if not self._token: return self.redirect_sso() - @rx.var - def check_auth(self): - return True if self.token else False + @rx.var(cache=True) + def check_auth(self) -> bool: + return True if self._token else False + + @rx.var(cache=True) + def token(self) -> Dict[str, str]: + return self._token def logout(self): - self.token = {} + self._token = {} return rx.redirect(authority + "/oauth2/v2.0/logout") def callback(self): - query_components = parse_qs(urlparse(self.router.page.raw_path).query) + query_components = self.router.page.params auth_response = { - "code": query_components["code"][0], - "client_info": query_components["client_info"][0], - "state": query_components["state"][0], - "session_state": query_components["session_state"][0], + "code": query_components.get("code"), + "client_info": query_components.get("client_info"), + "state": query_components.get("state"), + "session_state": query_components.get("session_state"), "client-secret": client_secret, } - result = sso_app.acquire_token_by_auth_code_flow( - self.flow, auth_response, scopes=[] - ) - self.access_token = result[ - "access_token" - ] # this can be used for accessing graph - self.token = result["id_token_claims"] + try: + result = sso_app.acquire_token_by_auth_code_flow( + self._flow, auth_response, scopes=[] + ) + except Exception as e: + return rx.toast(f"error something went wrong") + # this can be used for accessing graph + self._access_token = result.get("access_token") + self._token = result.get("id_token_claims") return rx.redirect(login_redirect) diff --git a/azure-auth/azure_auth/pages/__init__.py b/azure-auth/azure_auth/pages/__init__.py index d298423f..5804a1ba 100644 --- a/azure-auth/azure_auth/pages/__init__.py +++ b/azure-auth/azure_auth/pages/__init__.py @@ -12,13 +12,16 @@ def logout() -> rx.Component: return rx.container("Logged out") -@rx.page(route="/home", on_load=SsoState.require_auth) +@rx.page(route="/", on_load=SsoState.require_auth) def home() -> rx.Component: return rx.container(rx.cond(SsoState.check_auth, auth_view(), unauth_view())) def auth_view() -> rx.Component: - return rx.text(f"Hello {SsoState.token['name']}") + return rx.vstack( + rx.text(f"Hello {SsoState.token['name']}"), + rx.button("logout", on_click=SsoState.logout), + ) def unauth_view() -> rx.Component: