Getting AADSTS50013 while trying to create GraphServiceClient to get user mail inboxes

joaoaddyai 0 Reputation points
2024-07-17T15:00:04.8466667+00:00

Hello.

I have a frontend nextjs application with microsoft entra login, I get the access and refresh token and store them.

I have then a python backend that does api calls of microsoft to get user inboxes and emails

I am getting right now the error

{"error":"invalid_grant","error_description":"AADSTS50013: Assertion failed signature validation. [Reason - Key was found, but use of the key to verify the signature failed., Thumbprint of key used by client

What is the fix that I need to do on the OnBehalfOfCredential?

I have this scopes

User.Read Mail.Read email Mail.ReadBasic Mail.ReadWrite

My code

"""
Microsoft API Service
"""
import os.path
import logging
import configparser
import datetime

from flask import Flask, g, request, jsonify
from flask_cors import CORS
from src.routes.emails.messages import email_messages
from src.routes.labels.labels import labels_route
from src.config.graph import Graph
from src.config.tokenCredentials import RefreshableTokenCredential

# from src.routes.aifeatures import aifeatureUtils
from src.shared import db


app = Flask(__name__)
# app.register_blueprint(aifeatureUtils, url_prefix="/ai")
app.config['CORS_HEADERS'] = 'Content-Type'
cors = CORS(app, resources={r"/*": {"origins": "*"}})

# logger = logging.getLogger(__name__)
# logging.getLogger().setLevel(logging.INFO)
# logging.getLogger().addHandler(handler)

def authenticate_api_key(api_key):
    return api_key in db.get_all_api_keys()

@app.before_request
def before_request_api_check():
    g.email = request.headers.get("email") or request.args.get("userId")
    api_key = request.headers.get('API-Key')
    if not api_key or not authenticate_api_key(api_key):
        return jsonify({'error': 'Unauthorized'}), 401
    
@app.before_request
def before_request():
    config = configparser.ConfigParser()
    config.read(['config.cfg', 'config.dev.cfg'])
    azure_settings = config['azure']

    try:
        print("Getting account data")
        g.user = db.get_account(g.email)
        g.access_token = g.user['access_token']  # Assuming the user's access token is stored in the account data
        g.refresh_token = g.user['refresh_token']
        g.expires_at = g.user['expires_at']
        g.email = g.user['email']

    except Exception as e:
        app.logger.error("%s Error getting account data: %s", g.email, str(e))
        return {
            "success": False,
            "error": f"Error getting account data: {str(e)}"
        }, 400

    graph: Graph = Graph(azure_settings)
    g.microsoft_service = graph

#Registering the blueprints
app.register_blueprint(email_messages)
app.register_blueprint(labels_route)

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 5051)))

from flask import Blueprint, g, jsonify

labels_route = Blueprint('labels_route', __name__, template_folder='templates')

@labels_route.route("/get-labels", methods=["GET"])
async def get_labels_sync():
    try:
        print("Getting labels")
        response = await g.microsoft_service.user_client.me.mail_folders.get()
        
        return {
            "success": True,
            "labels": response
        }

    except Exception as e:
        print(f"Error getting labels: {str(e)}")
        return {
            "success": False,
            "error": f"Error getting labels: {str(e)}"
        }, 400

from configparser import SectionProxy
from azure.identity import DeviceCodeCredential, OnBehalfOfCredential
from msgraph import GraphServiceClient
from azure.core.credentials import AccessToken
from flask import g

class Graph:
    settings: SectionProxy
    device_code_credential: DeviceCodeCredential
    access_token_credential: AccessToken
    user_client: GraphServiceClient

    def __init__(self, config):
        self.settings = config
        client_id = self.settings['clientId']
        tenant_id = self.settings['tenantId']
        graph_scopes = self.settings['graphUserScopes'].split(' ')

        self.device_code_credential = DeviceCodeCredential(client_id, tenant_id = tenant_id)

        credential = OnBehalfOfCredential(
            tenant_id=tenant_id,
            client_id=client_id,
            client_secret=self.settings['clientSecret'],
            user_assertion=g.access_token
        )

        self.user_client = GraphServiceClient(credentials=credential, scopes=graph_scopes)


Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
11,618 questions
Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
20,924 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Danstan Onyango 3,821 Reputation points Microsoft Employee
    2024-07-18T11:53:58.26+00:00

    What requirement binds you to use OBO flow? If you don't have to use OBO flow you can use auth code flow like below.

    import asyncio
    from azure.identity import InteractiveBrowserCredential
    from msgraph import GraphServiceClient
    
    # Create a credential object. Used to authenticate requests 
    credentials = InteractiveBrowserCredential(
        client_id=os.getenv('client_id'),
        tenant_id=os.getenv('tenant_id'),
    )
    
    scopes = ["User.Read"]
    
    # Create an API client with the credentials and scopes.
    client = GraphServiceClient(credentials=credential, scopes=scopes)
    
    # GET A USER USING THE USER ID (GET /users/{id})
    async def get_user():
        user = await client.users_by_id('USER_ID').get()
        if user:
            print(user.user_principal_name, user.display_name, user.id)
    asyncio.run(get_user())
    
    

    Regardless though, once you have the credential, you create a Graph client like this

    from msgraph import GraphServiceClient
    
    client = GraphServiceClient(credentials=credential, scopes=scopes)
    
    ## Make call
    
    await client.users.by_user_id('USER_ID').get()
    
    
    

    See https://github.com/microsoftgraph/msgraph-sdk-python/blob/main/docs/authentication_samples.md for more details