diff --git a/docs/backends/azure_ad.rst b/docs/backends/azure_ad.rst new file mode 100644 index 000000000..160682523 --- /dev/null +++ b/docs/backends/azure_ad.rst @@ -0,0 +1,24 @@ +Microsoft Azure Active Directory / Office365 +============================================ + +Azure Active Directory uses OAuth2 for its connect workflow. + +- Register a new application at `Microsoft Azure Portal`_, set your site + domain as redirect domain, + +- Fill ``Client Id`` and ``Client Secret`` values in the settings:: + + SOCIAL_AUTH_AZURE_AD_KEY = '' + SOCIAL_AUTH_AZURE_AD_SECRET = '' + +- Also it's possible to define extra permissions with:: + + SOCIAL_AUTH_AZURE_AD_SCOPE = [...] + + Default is empty, which is enough to retrieve the user's first name, + last name and email. + +- Make sure to have a valid ``Redirect URL`` (``http://your-domain/complete/azure_ad``) + defined for the application in the portal. + +.. _Microsoft Azure Portal: https://manage.windowsazure.com/ diff --git a/docs/backends/index.rst b/docs/backends/index.rst index d42e414ef..0aafd2d20 100644 --- a/docs/backends/index.rst +++ b/docs/backends/index.rst @@ -50,6 +50,7 @@ Social backends angel aol appsfuel + azure_ad battlenet beats behance diff --git a/social/backends/azure_ad.py b/social/backends/azure_ad.py new file mode 100644 index 000000000..af74ad47c --- /dev/null +++ b/social/backends/azure_ad.py @@ -0,0 +1,67 @@ +""" +Azure Active Directory/Office365 authentication backend + +The flow here is a little different from regular OAuth2 flow, +because the id_token returned by Microsoft already contains +basic information (user first name, last name, and username +which is an email address) and we only need to decode it, +so no further request for details from server is made. +""" +import jwt + +from social.backends.oauth import BaseOAuth2 + + +class AzureADAuth(BaseOAuth2): + name = 'azure_ad' + AUTHORIZATION_URL = 'https://login.microsoftonline.com/common/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/token' + REDIRECT_STATE = False + + # ID_KEY should name a unique ID for the user. Microsoft documentation for + # the data we use (id_token claims) says: + # oid -- Object identifier (ID) of the user object in Azure AD. + # sub -- Token subject identifier. This is a persistent and immutable identifier + # for the user that the token describes. Use this value in caching logic. + # We picked oid. + ID_KEY = 'oid' + + def auth_complete_params(self, state=None): + # Specify a harmless resource + params = super(AzureADAuth, self).auth_complete_params(state) + # The resource name of your app is supposed to work here, but we found it didn't. + params['resource'] ='https://api.office.com/discovery/' + return params + + def user_data(self, access_token, *args, **kwargs): + """Loads user data""" + # As noted above, most backends issue an API request here, but we + # can get the basic data by just decoding the id_token we already have. + response = kwargs.get('response', {}) + id_token = response.get('id_token') + if id_token: + try: + claims = jwt.decode(id_token, verify=False) + return claims + except jwt.DecodeError: + # Token was not really jwt claims + pass + except: + import traceback; traceback.print_exc() + raise + return {} + + def get_user_details(self, response): + """Return user details from AzureAD id_token claims""" + fullname, first_name, last_name = self.get_user_names( + first_name=response.get('given_name'), + last_name=response.get('family_name'), + fullname=response.get('name') + ) + email = response['unique_name'] + return {'username': email, + 'email': email, + 'fullname': fullname, + 'first_name': first_name, + 'last_name': last_name} +