Search code examples
azureoauth-2.0azure-active-directoryjwtazure-ad-msal

AzureAD OIDC: access_token from https://sts.windows.net/ is not a JWT token


I'm implementing the authorization_code flow with AzureAD OIDC for my application in python using MSAL library.

I need to use v1 OAuth2 endpoints in order to require MFA authentication (which doesn't seem to be availabile in v2.0), so:

https://login.microsoftonline.com/{tenant}/oauth2/

Instead of

https://login.microsoftonline.com/{tenant}/oauth2/v2.0/

Here's the sample code:

msal_app = ConfidentialClientApplication(
    client_id="xxx",
    client_credential="yyy",
    oidc_authority="https://sts.windows.net/{tenant}",  # Force OAuth2 v1.0
)

...

flow = msal_app.initiate_auth_code_flow(
    scopes=["User.Read"],
    redirect_uri="zzz",
)

# Redirect to: flow["auth_uri"] + "&amr_values=ngcmfa"  # OAuth2 v2.0 does not accept amr_values which requires the MFA authentication

...

payload = msal_app.acquire_token_by_auth_code_flow(flow, dict(request.query_params))

Here's what the payload contains:

enter image description here

It worked, but the access_token is weird, it does not appear to be a JWT token (which usually begins with ey).

If I try to use it with Microsoft Graph to use the User.Read scope I asked for, it fails:

requests.get(
        "https://graph.microsoft.com/v1.0/me",
        headers={"Authorization": f"Bearer {payload['access_token']}"}
    ).json()
{'error': {'code': 'InvalidAuthenticationToken', 'message': "IDX14100: JWT is not well formed, there are no dots (.).\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.", 'innerError': {'date': '2024-06-03T08:58:59', 'request-id': 'qqq', 'client-request-id': 'www'}}}

So question: why is it giving an access_token that cannot be used?

Note: If I switch to OAuth2 v2.0, it works without issues and returns a valid JWT token in access_token, however, I cannot use amr_values to enforce MFA, which is a requirement.

Can you help me understanding what is going on?


Solution

  • Note that: To force Multiple factor authentication, you need to pass amr_values in the authorize URL. Refer this MsDoc

    • And it supports only v1.0 endpoint.
    • In you code try to change authority as https://login.microsoftonline.com/TenantID. Refer this MsDoc

    For sample, I modified the code by passing the authorize URL directly and got the results successfully:

    # Create a Flask web application
    app = Flask(__name__)
    app.secret_key = 'os.urandom(24)'  
     
    client_id = "ClientID"
    tenant_id = "TenantID"
    redirect_uri = "RedirectURL"
     
    msal_app = ConfidentialClientApplication(
        client_id=client_id,
        authority="https://login.microsoftonline.com/" + tenant_id
    )
    # Define the authorization endpoint URL
    auth_url = (
        "https://login.microsoftonline.com/"
        + tenant_id
        + "/oauth2/authorize?"
        + "response_type=code&"
        + "client_id=" + client_id + "&"
        + "state=12345&"
        + "resource=https://graph.microsoft.com&"
        + "client-request-id=67890&"
        + "amr_values=ngcmfa&" 
        + "redirect_uri=" + redirect_uri
    )
    @app.route("/")
    def home():
        return redirect(auth_url)
    @app.route("/get_a_token")
    def get_a_token():
        auth_code = request.args.get('code')
        # Acquire an access token using the authorization code
        try:
            token_response = msal_app.acquire_token_by_authorization_code(
                auth_code,
                scopes=["User.Read"],
                redirect_uri=redirect_uri
            )
        except Exception as e:
            return f"Token acquisition failed: {str(e)}"
        if "access_token" in token_response:
            access_token = token_response["access_token"]
            print("Access Token:", access_token)
            # Use the access token to make a request to Microsoft Graph
            graph_response = requests.get(
                "https://graph.microsoft.com/v1.0/me",
                headers={"Authorization": f"Bearer {access_token}"}
            )
            if graph_response.status_code == 200:
                user_data = graph_response.json()
                return f"User: {user_data['displayName']}, Email: {user_data['mail']}"
            else:
                return f"Failed to fetch user data from Microsoft Graph: {graph_response.text}"
        else:
            error_message = token_response.get("error_description", "Unknown error")
            return f"Token acquisition failed: {error_message}"
    # Run the Flask application
    if __name__ == "__main__":
        app.run(debug=True)
    

    The user is redirected to MFA page:

    enter image description here

    And after successful sign-in I got the access token:

    enter image description here

    Using the access token, I am able to successfully query Micrsoft Graph API like below:

    enter image description here

    Reference:

    Azure Active Directory multi-factor check for authorization - Stack Overflow by Joey Cai