Closed kenricci closed 6 months ago
Hi, I have in fact implemented what you have suggested in a prior project but chose not to include it in this package as there is no single/correct way of doing it, and I wanted to keep this package for standalone Django apps only.
In any case, it is quite simple to achieve by subclassing the DRF authentication.BasicAuthentication
class like so:
class BaseAzureAuthentication(authentication.BasicAuthentication, GetUserMixin):
"""
A BasicAuthentication subclass that allows a user to authenticate directly
with the API using their Azure AD username and password.
Raises AuthenticationFailed if the username/password is incorrect or if
there was a problem authenticating with Azure AD (i.e. user suspended).
Implemented as a base class so we can more obviously show how a user can
authenticate
Usage:
import requests
resp = requests.get(<api_url>, auth=(<azure username>, <azure password>))
"""
def authenticate_credentials(self, userid, password, request=None):
"""
For the given username and password will authenticate the user with Azure
and then return a ~users.models.User instance instantiated with the
users Azure credentials
:param userid: The Azure users username (email address)
:param password: The Azure users password
:param request: The request object
:return: Instance of ~users.models.User
:raises: exceptions.AuthenticationFailed
"""
msal_app = msal.ConfidentialClientApplication(
client_id=settings.AZURE_CLIENT_ID,
client_credential=settings.AZURE_CLIENT_SECRET,
authority=settings.AZURE_AUTHORITY,
)
token_result = msal_app.acquire_token_by_username_password(
userid, password, scopes=["User.Read"]
)
if "error" not in token_result:
access_token = token_result["access_token"]
sub_claim = token_result["id_token_claims"]["sub"]
resp = requests.get(
settings.GRAPH_API_URL,
headers={"Authorization": f"Bearer {access_token}"},
)
if resp.ok:
user = resp.json()
# The Azure `user["id"]` contains the `oid` claim which is the same
# across different apps for the same tenant, for any given user, so
# we need to replace it with the `sub` claim in `user_id`
user["id"] = sub_claim
return self.get_user(user)
# Give the user a hint about what could be wrong with their account
raise exceptions.AuthenticationFailed(
_(f"Failed Azure authentication: `{resp.json()['error']}`")
)
# Returning a generic error avoids leaking Azure details to the client
raise exceptions.AuthenticationFailed(_("Invalid username/password."))
Have you considered adding the ability for a user to request a token via RestAPI using username and password with Azure AD authentication?