Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions azure_auth/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ def get_token_from_cache(self):
]
return token_result

def wam_login(self) -> Optional[dict]:
"""
Initiates the WAM login flow and returns the token result.
"""
result = self.msal_app.acquire_token_interactive(
scopes=settings.AZURE_AUTH["SCOPES"],
prompt=settings.AZURE_AUTH.get("PROMPT", None),
parent_window_handle=msal.PublicClientApplication.CONSOLE_WINDOW_HANDLE,
)
if "error" in result:
print(f"WAM login error: {result}")
result = None
return result

def authenticate(self, token: dict) -> AbstractBaseUser:
"""
Helper method to authenticate the user. Gets the Azure user from the
Expand Down
13 changes: 11 additions & 2 deletions azure_auth/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
from django.urls import path
from django.conf import settings

from azure_auth.views import azure_auth_callback, azure_auth_login, azure_auth_logout
from azure_auth.views import (
azure_auth_callback,
azure_auth_login,
azure_auth_logout,
wam_auth_login,
)
from azure_auth.utils import is_broker_enabled

app_name = "azure_auth"

_login_function = azure_auth_login if not is_broker_enabled(settings.AZURE_AUTH) else wam_auth_login
urlpatterns = [
path("login", azure_auth_login, name="login"),
path("login", _login_function, name="login"),
path("logout", azure_auth_logout, name="logout"),
path("callback", azure_auth_callback, name="callback"),
]
28 changes: 28 additions & 0 deletions azure_auth/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from typing import Any


class EntraStateSerializer:
Expand All @@ -10,3 +11,30 @@ def deserialize(self, state: str):
return json.loads(state)
except json.JSONDecodeError:
return {}


def enable_broker_on_windows(azure_auth: dict[str, Any]) -> None:
"""
Modifies the given azure_auth settings dictionary to enable brokered authentication.
This assumes the current platform is Windows.

For details, see:
Using MSAL Python with Web Account Manager
https://learn.microsoft.com/en-us/entra/msal/python/advanced/wam
"""

# WAM only makes sense for public clients. The Azure Portal also needs to have
# the correct redirect URL configured:
# ms-appx-web://microsoft.aad.brokerplugin/YOUR_CLIENT_ID
azure_auth["CLIENT_TYPE"] = "public_client"
additional_client_kwargs = azure_auth.get("ADDITIONAL_CLIENT_KWARGS", {})
additional_client_kwargs["enable_broker_on_windows"] = True
azure_auth["ADDITIONAL_CLIENT_KWARGS"] = additional_client_kwargs


def is_broker_enabled(azure_auth: dict[str, Any]) -> bool:
"""Returns True if brokered authentication is enabled in the given settings,
False otherwise."""
return azure_auth.get("ADDITIONAL_CLIENT_KWARGS", {}).get(
"enable_broker_on_windows", False
)
21 changes: 21 additions & 0 deletions azure_auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,24 @@ def azure_auth_callback(request: HttpRequest):
return HttpResponseRedirect(next)
return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
return HttpResponseForbidden("Invalid email for this app.")


def wam_auth_login(request: HttpRequest):
"""
This view is used to handle Windows Authentication Manager (WAM) login.
"""
# WAM flow is synchrous, so we get a token immediately. Hence this function
# also includes the callback logic.
token = AuthHandler(request).wam_login()
if not token:
return HttpResponseForbidden("WAM login failed.")

user = authenticate(request, token=token)
if user:
login(request, user)

next = request.GET.get("next")
if next and url_has_allowed_host_and_scheme(next, allowed_hosts=None):
return HttpResponseRedirect(next)
return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
return HttpResponseForbidden("Invalid email for this app.")
Loading