Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

consumer_key consumer_secret for Windows Live Oauth2 #187

Open
jeffoneill opened this issue Nov 19, 2017 · 7 comments
Open

consumer_key consumer_secret for Windows Live Oauth2 #187

jeffoneill opened this issue Nov 19, 2017 · 7 comments

Comments

@jeffoneill
Copy link

I'm trying to setup Windows Live Oauth2, but I'm having trouble knowing what to use for the consumer_key and the consumer_secret. Below is a screen shot from this site: https://apps.dev.microsoft.com/?mkt=en-us#/appList

For consumer_key, I'm using the "Application Id" in the screenshot. For consumer_secret, I've tried:

  • ttf**************************** (the real values and not the asterisks of course)
  • The string underneath that ttf*** that starts with D and ends in 5.
  • A very long certificate from a cert.pfx that was downloaded when I generated the key pair

In all cases, I get the error: "The client does not have a secret configured." Any help would be appreciated.

mslive

@jeffoneill
Copy link
Author

It look like Microsoft has completely revamped its Oauth2 login. Authomatic seems to support v1 and the latest is v2. It looks like v1 credentials still work but if you create new credentials, they will be v2 credentials what won't work with Authomatic.

To answer my previous question, the ttf*** is the right secret key to use. I'm working on creating a new WindowsLiveV2 Oauth2 class but it is slow going because Oauth2 details are all new to me.

The code below is getting me part of the way there and I'll keep working on it.

class WindowsLiveV2(OAuth2):

    user_authorization_url = 'https://login.microsoftonline.com/common/oauth2/authorize'
    access_token_url = 'https://login.microsoftonline.com/common/oauth2/token'
    user_info_url = 'https://apis.live.net/v5.0/me'
            
    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):
        if request_type is cls.ACCESS_TOKEN_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['response_type'] = 'code'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)
        return request_elements

@jeffoneill
Copy link
Author

I'm getting close with this code:

class WindowsLiveV2(OAuth2):

    user_authorization_url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
    access_token_url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
    user_info_url = ''
            
    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):
        if request_type is cls.USER_AUTHORIZATION_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['response_type'] = 'code'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)
        if request_type is cls.ACCESS_TOKEN_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['grant_type'] = 'authorization_code'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)
        return request_elements
                
    @classmethod
    def _x_credentials_parser(cls, credentials, data):
        if data.get('token_type') == 'bearer':
            credentials.token_type = cls.BEARER
        return credentials

I get this error:

Failed to obtain OAuth 2.0 access token from https://login.microsoftonline.com/common/oauth2/v2.0/token! HTTP status: 200, message: {"id_token":"..."}

I'm using this for the scope: scope=["openid email profile"]

I expect that I need to do something different in _x_credentials_parser. Hopefully someone more familiar with Authomatic can help.

@jeffoneill
Copy link
Author

Sorry the serial posts about this. I kept thinking I was going to stop but I kept working on it, and now I have something working. Here is some code with comments:

class WindowsLiveV2(OAuth2):
    """
    The scopes are really confusing here.  MS has two types of scopes:
    (i) openid scopes to get user info right away and (ii) graph scopes for
    using a received access token.

    If you use only openid scopes, then MS returns only an id_token that has 
    the user info, but you still need to validate it. If you use only graph 
    scopes, then don't get an id_token and you need to do a graph call to get 
    user info. If you use both openid and graph scopes, then you get both the 
    id_token and the authorization. You can then use the authorization to get 
    an access token, which again returns the id_token which is presumably 
    validated.
        
    Microsoft says: "When your app receives an ID token, it must validate the 
    signature to prove the token's authenticity and validate a few claims in 
    the token to prove its validity."  But since we are getting the id_token
    directly from Microsoft with the access token after authorization, it seems
    unneccessary.
    See https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-tokens
    
    Using these scopes is recommended (MS uses space for a separator):
        scope=["openid email profile https://graph.microsoft.com/user.read"]
    """

    user_authorization_url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
    access_token_url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
    user_info_url = ''
            
    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):
        if request_type is cls.USER_AUTHORIZATION_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['response_type'] = 'code'
            params['response_mode'] = 'query'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)
        if request_type is cls.ACCESS_TOKEN_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['grant_type'] = 'authorization_code'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)
        return request_elements
                
    @classmethod
    def _x_credentials_parser(cls, credentials, data):
        if data.get('token_type') == 'bearer':
            credentials.token_type = cls.BEARER
        return credentials
    
    @staticmethod
    def _x_user_parser(user, data):
        # If you use the "openid" scope, then authorization will return a 
        # id_token that contains the user's email and name. The id_token
        # also includes an oid (i.e., user id) that you could use to make
        # another call to Microsoft to get additional information.
        id_token = data.get('id_token')
        payload = id_token.split(".")[1] # Strip header and signature
        if len(payload) % 4:
            payload += '=' * (4 - len(payload) % 4)        
        ms_user = json.loads(base64.b64decode(payload))
        user.email = ms_user["email"] if "email" in ms_user else ""
        user.name = ms_user["name"] if "name" in ms_user else ""
        return user

See the comment in the docstring regarding the scopes to use with this.

I'm not sure if you need to further validate the user info received in id_token but I asked a question on StackOverflow about that so hopefully will have an answer to that soon.

@jensens
Copy link
Member

jensens commented Nov 20, 2017

Thanks for the work! Do you think you can provide a Pull-Request with a fixed provider?

@jeffoneill
Copy link
Author

Sure, I've never done a pull request so give me some time to figure that out.

Do you know if the existing Windows Live provider still works? If so, we should this as an additional provider instead of replacing the existing one.

@jeffoneill
Copy link
Author

Here is an update to the above code so that it works with Microsoft business accounts. For some bizarre reason, Microsoft returns different info depending on whether it is a business or personal account.

class WindowsLiveV2(OAuth2):
    """
    The scopes are really confusing here.  MS has two types of scopes:
    (i) openid scopes to get user info right away and (ii) graph scopes for
    using a received access token.

    If you use only openid scopes, then MS returns only an id_token that has
    the user info, but you still need to validate it. If you use only graph
    scopes, then don't get an id_token and you need to do a graph call to get
    user info. If you use both openid and graph scopes, then you get both the
    id_token and the authorization. You can then use the authorization to get
    an access token, which again returns the id_token which is presumably
    validated.

    Microsoft says: "When your app receives an ID token, it must validate the
    signature to prove the token's authenticity and validate a few claims in
    the token to prove its validity."  But since we are getting the id_token
    directly from Microsoft with the access token after authorization, it seems
    unneccessary.
    See https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-tokens

    Using these scopes is recommended (MS uses space for a separator):
        scope=["openid email profile https://graph.microsoft.com/user.read"]
    """

    user_authorization_url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
    access_token_url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
    user_info_url = ''

    @classmethod
    def _x_request_elements_filter(cls, request_type, request_elements,
                                   credentials):
        if request_type is cls.USER_AUTHORIZATION_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['response_type'] = 'code'
            params['response_mode'] = 'query'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)
        if request_type is cls.ACCESS_TOKEN_REQUEST_TYPE:
            url, method, params, headers, body = request_elements
            params['grant_type'] = 'authorization_code'
            request_elements = core.RequestElements(url, method, params,
                                                    headers, body)
        return request_elements

    @classmethod
    def _x_credentials_parser(cls, credentials, data):
        if data.get('token_type') == 'bearer':
            credentials.token_type = cls.BEARER
        return credentials

    @staticmethod
    def _x_user_parser(user, data):
        # If you use the "openid" scope, then authorization will return a
        # id_token that contains the user's email and name. The id_token
        # also includes an oid (i.e., user id) that you could use to make
        # another call to Microsoft to get additional information.
        id_token = data.get('id_token')
        payload = id_token.split(".")[1] # Strip header and signature
        if len(payload) % 4:
            payload += '=' * (4 - len(payload) % 4)
        ms_user = json.loads(base64.b64decode(payload))
        user.email = ms_user["email"] if "email" in ms_user else ""
        # Sometimes the email field is blank but is in preferred_username!?!
        if not user.email and "preferred_username" in ms_user:
            logging.error(ms_user)
            user.email = ms_user["preferred_username"]
        user.name = ms_user["name"] if "name" in ms_user else ""
        user.id = ms_user["oid"] if "oid" in ms_user else None
        return user

@mrichar1
Copy link
Member

After several years of inactivity, authomatic is now under community management, and we have just released a new stable version (1.0.0).

We are now reviewing all issues and PRs and hoping to begin work to solve as many of these as possible.

We are keen to find out which issues still apply, and which PRs are still required/are likely to merge cleanly into the current code. We are aiming to review them all, but any help with prioritisation would be very useful!

If you are still interested in having this issue/PR resolved, or are able to help us work on it, please reply to this message. That way we know which issues are most important to the community.

@jensens jensens added this to New: Needs Check in Issue-Management Jun 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Issue-Management
  
New: Needs Check
Development

No branches or pull requests

3 participants