最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Why do I keep getting "401 unauthorized" response when sending mails through Graph API with delegated

programmeradmin3浏览0评论

I have created an app on Azure to send emails using Graph API with delegated type access

I'm using an interactive flow to generate auth code and access token using oauth2/v2.0/authorize and oauth2/v2.0/token endpoints using the following code

import requests
import webbrowser
import urllib.parse

class GraphEmailSender:
    def __init__(self, client_id, tenant_id):
        """
        Initialize Graph Email Sender
        
        :param client_id: Azure AD application client ID
        :param tenant_id: Azure AD tenant ID
        """
        self.client_id = client_id
        self.tenant_id = tenant_id
        
        # OAuth endpoints
        self.authorize_url = f'/{tenant_id}/oauth2/v2.0/authorize'
        self.token_url = f'/{tenant_id}/oauth2/v2.0/token'
        
        # Graph API endpoint
        self.graph_endpoint = '.0'
        
        # Redirect URI (must be registered in Azure AD app)
        self.redirect_uri = 'http://localhost:8000/callback'
        
        # Scopes for email sending
        self.scopes = [
            '.Send',
            '.Read',
            'offline_access'
        ]
    
    def get_authorization_code(self):
        """
        Generate authorization URL and prompt for manual code entry
        
        :return: Authorization code
        """
        # Prepare authorization request parameters
        auth_params = {
            'client_id': self.client_id,
            'response_type': 'code',
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'response_mode':'fragment'
        }
        
        # Construct authorization URL
        auth_url = f"{self.authorize_url}?{urllib.parse.urlencode(auth_params)}"
        
        # Open browser for authorization
        print("Please authorize the application:")
        webbrowser.open(auth_url)
        
        # Manually enter authorization code
        auth_code = input("Enter the authorization code from the redirect URL: ")
        return auth_code
    
    def exchange_code_for_token(self, authorization_code):
        """
        Manually exchange authorization code for access token
        
        :param authorization_code: Authorization code
        :return: Access token
        """
        # Prepare token exchange parameters
        token_params = {
            'client_id': self.client_id,
            'grant_type': 'authorization_code',
            'code': authorization_code,
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'client_secret':'Ixxxx'
        }
        
        # Send token request
        response = requests.post(
            self.token_url, 
            data=token_params,
            headers={'Content-Type': 'application/x-www-form-urlencoded'}
        )
        # Return access token
        return response.json().get('access_token')
    
    def send_email(self, access_token, to_email, subject, body):
        """
        Send email using access token
        
        :param access_token: OAuth access token
        :param to_email: Recipient email address
        :param subject: Email subject
        :param body: Email body
        :return: Boolean indicating success
        """
        # Prepare email message
        email_message = {
            "message": {
                "subject": subject,
                "body": {
                    "contentType": "Text",
                    "content": body
                },
                "toRecipients": [
                    {
                        "emailAddress": {
                            "address": to_email
                        }
                    }
                ],
                "from":{
                    "emailAddresss":{
                        "address":"[email protected]"
                    }
                }
            },
            "saveToSentItems": "true"
        }
        
        # Prepare headers
        headers = {
            'Authorization': f'Bearer {access_token}',
            'Content-Type': 'application/json'
        }
        try:
            # Send email via Graph API
            response = requests.post(
                f'{self.graph_endpoint}/me/sendMail', 
                json=email_message, 
                headers=headers
            )
            
            # Check response
            if response.status_code in [200, 201, 202]:
                print("Email sent successfully!")
                return True
            else:
                print(f"Failed to send email. Status code: {response.status_code} {response}")
                return False
        
        except Exception as e:
            print(f"Error sending email: {e}")
            return False

# Example usage
def main():
    # Replace with your actual Azure AD application details
    TENANT_ID = '97sss'
    CLIENT_ID = '803csss'
    
    # Create email sender
    email_sender = GraphEmailSender(CLIENT_ID, TENANT_ID)
    
    # Get authorization code (manual process)
    auth_code = email_sender.get_authorization_code()
    
    # Exchange code for access token
    access_token = email_sender.exchange_code_for_token(auth_code)
    
    # Send email
    email_sender.send_email(
        access_token,
        to_email='[email protected]',
        subject='OAuth Email Test',
        body='Email sent using simplified OAuth flow.'
    )

if __name__ == '__main__':
    main()

It seems like the access token that is generated also has all the permissions(scope) i.e., Mail.Send,User.Read

But, in the end, I got 401 unauthorized error.

Failed to send email. Status code: 401 <Response [401]>

I have also set the redirect_uri and supported account type to all accounts

Do I need to use an Office365 account or I can use any Microsoft account to send and receive emails?

Edit: Here's the screenshot of the error code and output


I have created an app on Azure to send emails using Graph API with delegated type access

I'm using an interactive flow to generate auth code and access token using oauth2/v2.0/authorize and oauth2/v2.0/token endpoints using the following code

import requests
import webbrowser
import urllib.parse

class GraphEmailSender:
    def __init__(self, client_id, tenant_id):
        """
        Initialize Graph Email Sender
        
        :param client_id: Azure AD application client ID
        :param tenant_id: Azure AD tenant ID
        """
        self.client_id = client_id
        self.tenant_id = tenant_id
        
        # OAuth endpoints
        self.authorize_url = f'https://login.microsoftonline/{tenant_id}/oauth2/v2.0/authorize'
        self.token_url = f'https://login.microsoftonline/{tenant_id}/oauth2/v2.0/token'
        
        # Graph API endpoint
        self.graph_endpoint = 'https://graph.microsoft/v1.0'
        
        # Redirect URI (must be registered in Azure AD app)
        self.redirect_uri = 'http://localhost:8000/callback'
        
        # Scopes for email sending
        self.scopes = [
            'https://graph.microsoft/Mail.Send',
            'https://graph.microsoft/User.Read',
            'offline_access'
        ]
    
    def get_authorization_code(self):
        """
        Generate authorization URL and prompt for manual code entry
        
        :return: Authorization code
        """
        # Prepare authorization request parameters
        auth_params = {
            'client_id': self.client_id,
            'response_type': 'code',
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'response_mode':'fragment'
        }
        
        # Construct authorization URL
        auth_url = f"{self.authorize_url}?{urllib.parse.urlencode(auth_params)}"
        
        # Open browser for authorization
        print("Please authorize the application:")
        webbrowser.open(auth_url)
        
        # Manually enter authorization code
        auth_code = input("Enter the authorization code from the redirect URL: ")
        return auth_code
    
    def exchange_code_for_token(self, authorization_code):
        """
        Manually exchange authorization code for access token
        
        :param authorization_code: Authorization code
        :return: Access token
        """
        # Prepare token exchange parameters
        token_params = {
            'client_id': self.client_id,
            'grant_type': 'authorization_code',
            'code': authorization_code,
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'client_secret':'Ixxxx'
        }
        
        # Send token request
        response = requests.post(
            self.token_url, 
            data=token_params,
            headers={'Content-Type': 'application/x-www-form-urlencoded'}
        )
        # Return access token
        return response.json().get('access_token')
    
    def send_email(self, access_token, to_email, subject, body):
        """
        Send email using access token
        
        :param access_token: OAuth access token
        :param to_email: Recipient email address
        :param subject: Email subject
        :param body: Email body
        :return: Boolean indicating success
        """
        # Prepare email message
        email_message = {
            "message": {
                "subject": subject,
                "body": {
                    "contentType": "Text",
                    "content": body
                },
                "toRecipients": [
                    {
                        "emailAddress": {
                            "address": to_email
                        }
                    }
                ],
                "from":{
                    "emailAddresss":{
                        "address":"[email protected]"
                    }
                }
            },
            "saveToSentItems": "true"
        }
        
        # Prepare headers
        headers = {
            'Authorization': f'Bearer {access_token}',
            'Content-Type': 'application/json'
        }
        try:
            # Send email via Graph API
            response = requests.post(
                f'{self.graph_endpoint}/me/sendMail', 
                json=email_message, 
                headers=headers
            )
            
            # Check response
            if response.status_code in [200, 201, 202]:
                print("Email sent successfully!")
                return True
            else:
                print(f"Failed to send email. Status code: {response.status_code} {response}")
                return False
        
        except Exception as e:
            print(f"Error sending email: {e}")
            return False

# Example usage
def main():
    # Replace with your actual Azure AD application details
    TENANT_ID = '97sss'
    CLIENT_ID = '803csss'
    
    # Create email sender
    email_sender = GraphEmailSender(CLIENT_ID, TENANT_ID)
    
    # Get authorization code (manual process)
    auth_code = email_sender.get_authorization_code()
    
    # Exchange code for access token
    access_token = email_sender.exchange_code_for_token(auth_code)
    
    # Send email
    email_sender.send_email(
        access_token,
        to_email='[email protected]',
        subject='OAuth Email Test',
        body='Email sent using simplified OAuth flow.'
    )

if __name__ == '__main__':
    main()

It seems like the access token that is generated also has all the permissions(scope) i.e., Mail.Send,User.Read

But, in the end, I got 401 unauthorized error.

Failed to send email. Status code: 401 <Response [401]>

I have also set the redirect_uri and supported account type to all accounts

Do I need to use an Office365 account or I can use any Microsoft account to send and receive emails?

Edit: Here's the screenshot of the error code and output


Share Improve this question edited Mar 28 at 12:37 Lucifer Darknight asked Mar 28 at 12:00 Lucifer DarknightLucifer Darknight 495 bronze badges 5
  • @Lucifier Could you share the screenshot your your error code and edit the question? – Pratik Jadhav Commented Mar 28 at 12:21
  • @PratikJadhav Added the error code screenshot – Lucifer Darknight Commented Mar 28 at 12:37
  • Could you pls try by changing the scope to http://graph.microsoft/.default and let me know it works for you or not – Pratik Jadhav Commented Mar 28 at 12:48
  • Same error, 401 unauthorized – Lucifer Darknight Commented Mar 28 at 12:57
  • Please check below solution! @Lucifer Darknight – Pratik Jadhav Commented Mar 28 at 13:27
Add a comment  | 

1 Answer 1

Reset to default 1

Initially, I got the same error message like you by using your script:

I configured my app registration same like you, Registered Microsoft Entra ID application:

Added delegated type Mail.Send API permission and granted admin consent like below:

Use below modified Python Script:

import requests
import webbrowser
import urllib.parse
import json
import base64

class GraphEmailSender:
    def __init__(self, client_id, tenant_id, client_secret):
        """
        Initialize Graph Email Sender
        
        :param client_id: Azure AD application client ID
        :param tenant_id: Azure AD tenant ID
        :param client_secret: Azure AD application client secret
        """
        self.client_id = client_id
        self.tenant_id = tenant_id
        self.client_secret = client_secret

        # OAuth endpoints
        self.authorize_url = f'https://login.microsoftonline/{tenant_id}/oauth2/v2.0/authorize'
        self.token_url = f'https://login.microsoftonline/{tenant_id}/oauth2/v2.0/token'

        # Graph API endpoint
        self.graph_endpoint = 'https://graph.microsoft/v1.0'

        # Redirect URI (must be registered in Azure AD app)
        self.redirect_uri = 'https://jwt.ms'

        # Scopes for email sending
        self.scopes = [
            'Mail.Send',
            'User.Read',
            'offline_access'
        ]
    
    def get_authorization_code(self):
        """
        Generate authorization URL and prompt for manual code entry
        :return: Authorization code
        """
        auth_params = {
            'client_id': self.client_id,
            'response_type': 'code',
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'response_mode': 'query'
        }

        auth_url = f"{self.authorize_url}?{urllib.parse.urlencode(auth_params)}"

        print("Please authorize the application:")
        webbrowser.open(auth_url)

        auth_code = input("Enter the authorization code from the redirect URL: ")
        return auth_code.strip()
    
    def exchange_code_for_token(self, authorization_code):
        """
        Exchange authorization code for access token
        :param authorization_code: Authorization code
        :return: Access token
        """
        token_params = {
            'client_id': self.client_id,
            'grant_type': 'authorization_code',
            'code': authorization_code,
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'client_secret': self.client_secret
        }

        response = requests.post(
            self.token_url, 
            data=token_params,
            headers={'Content-Type': 'application/x-www-form-urlencoded'}
        )
        
        token_data = response.json()
        
        # Debug token response
        print("\nToken Response:", json.dumps(token_data, indent=2))
        
        if 'access_token' in token_data:
            return token_data['access_token']
        else:
            print("Failed to retrieve access token:", token_data.get("error_description", "Unknown error"))
            return None
    
    def decode_jwt(self, jwt_token):
        """
        Decode JWT token (for debugging scopes)
        """
        parts = jwt_token.split('.')
        if len(parts) < 2:
            print("Invalid JWT Token")
            return None
        payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
        print("\nDecoded JWT Payload:", json.dumps(payload, indent=2))
    
    def send_email(self, access_token, to_email, subject, body):
        """
        Send email using access token
        :param access_token: OAuth access token
        :param to_email: Recipient email address
        :param subject: Email subject
        :param body: Email body
        :return: Boolean indicating success
        """
        email_message = {
            "message": {
                "subject": subject,
                "body": {
                    "contentType": "Text",
                    "content": body
                },
                "toRecipients": [
                    {
                        "emailAddress": {
                            "address": to_email
                        }
                    }
                ]
            },
            "saveToSentItems": "true"
        }

        headers = {
            'Authorization': f'Bearer {access_token}',
            'Content-Type': 'application/json'
        }

        response = requests.post(
            f'{self.graph_endpoint}/me/sendMail', 
            json=email_message, 
            headers=headers
        )
        
        if response.status_code in [200, 201, 202]:
            print(" Email sent successfully!")
            return True
        else:
            print(f" Failed to send email. Status code: {response.status_code}")
            print("Response:", response.text)
            return False


# Example usage
def main():
    # Replace with your actual Azure AD application details
    TENANT_ID = '<TENANT_ID>'
    CLIENT_ID = '<CLIENT_ID>'
    CLIENT_SECRET = '<CLIENT_SECRET>'  # Keep this secret, avoid hardcoding

    email_sender = GraphEmailSender(CLIENT_ID, TENANT_ID, CLIENT_SECRET)

    # Get authorization code (manual process)
    auth_code = email_sender.get_authorization_code()

    # Exchange code for access token
    access_token = email_sender.exchange_code_for_token(auth_code)
    
    if access_token:
        # Decode JWT to check scopes
        email_sender.decode_jwt(access_token)

        # Send email
        email_sender.send_email(
            access_token,
            to_email='<RECIPENT_MAIL_ID>',
            subject='OAuth Email Test',
            body='Email sent using Graph API with OAuth.'
        )
    else:
        print(" Access token could not be obtained.")


if __name__ == '__main__':
    main()

Response:

Also, I've verified the same from Outlook portal under Sent Items tab:

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论