Azure POST login.microsoftonline.com/oauty2/token fails on AADSTS900144

Question:

I need to automate connecting to logs analytics on Azure. Before I can do that, I need to get an access token.

With different docs and https://www.youtube.com/watch?v=ujzrq8Fg9Gc, I am trying to set that up.


TRY1

My first try was using SoapUI to send a POST request to:

https://login.microsoftonline.com/MY TENANT ID/oauth2/token
?grant_type=client_credentials
&client_id=MY CLIENT ID
&redirect_uri=MY URL
&resource=https%3A%2F%2Fwestus2.api.loganalytics.io
&client_secret=MY CLIENT SECRET

With header:

Content-Type: application/x-www-form-urlencoded

I always get this response:

HTTP/1.1 400 Bad Request
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
x-ms-request-id: SOMETHING
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie: fpc=SOMETHING; expires=Mon, 05-Aug-2019 13:14:50 GMT; path=/; secure; HttpOnly
Set-Cookie: x-ms-gateway-slice=prod; path=/; secure; HttpOnly
Set-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly
Date: Sat, 06 Jul 2019 13:14:49 GMT
Content-Length: 437

{
    "error":"invalid_request",
    "error_description":"AADSTS900144: The request body must contain 
                         the following parameter: 'grant_type'.rn
                         Trace ID: SOMETHINGrn
                         Correlation ID: SOMETHINGrn
                         Timestamp: 2019-07-06 13:14:50Z",
    "error_codes":[900144],
    "timestamp":"2019-07-06 13:14:50Z",
    "trace_id":"SOMETHING",
    "correlation_id":"SOMETHING"
}

TRY 2

I wrote it in Python using import requests, like so:

import os,sys
import requests

Azure_client_id     = 'MY CLIENT ID'
Azure_redirect_uri  = 'MY URL'
Azure_client_secret = 'CLIENT SECRET'
Azure_tenant_id     = 'TENANT ID'
Azure_resource      = 'https://westus2.api.loganalytics.io'

###############################################################################
token_url = 'https://login.microsoftonline.com/' + Azure_tenant_id + '/oauth2/token'
token_headers = {
    'Content-type': 'application/x-www-form-urlencoded',
}
token_params = {
    'grant_type': 'client_credentials',
    'client_id': Azure_client_id,
    'redirect_uri': Azure_redirect_uri,
    'resource': Azure_resource,
    'client_secret': Azure_client_secret,
}

token_response = requests.post(token_url, headers=token_headers, params=token_params)
# This is to see what was sent
print(token_response.url + "nn")

# get the response and print it
token_result = ''
for chunk in token_response.iter_content(chunk_size=128):
    token_result = token_result + str(chunk)

print(token_result.replace("\n","n"))

The URL that was sent is this (formatted for readability):

https://login.microsoftonline.com/MY TENANT ID/oauth2/token
    ?grant_type=client_credentials
    &client_id=MY CLIENT ID
    &redirect_uri=https%3A%2F%2FMY URL
    &resource=https%3A%2F%2Fwestus2.api.loganalytics.io
    &client_secret=MY SECRET URL ENCODED

The response I get is this (reformatted for readability):

b'{"error":"invalid_request",
    "error_description":"AADSTS900144: The request body must contain 
                         the following parameter: 'grant_type'b"'.\r
                         Trace ID: SOMETHING\r
                         Correlation ID: SOMETHING\r
                         Timestamp: 2019-"b'07-06 13:49:59Z",
   "error_codes":[900144],
   "timestamp":"2019-07-06 13:49:59Z",
   "trace_id":"SOMETHING",
   "co'b'rrelation_id":"SOMETHING"}''

At least I get the same error (!). Since my requests clearly include a “grant_type” parameter, my guess is that there is either something wrong with the encoding (which is done by SoapUI and Python’s requests), something wrong with my URL, or I am not using the proper IDs.

Is there a way in Azure to validate that my client secret is valid? Once created, it cannot be read anymore. And someone else created this key so I cannot assume what he gave me is ok.

Any comment, hints, pointing out blatant mistakes on my part are appreciated.

Asked By: Nic3500

||

Answers:

Change

token_response = requests.post(token_url, headers=token_headers, params=token_params)

to

token_response = requests.post(token_url, data=token_params)

You don’t need to specify the Content-type header, it’s inferred from your payload (dictionary, so x-www-form-urlencoded), also data is what you want (payload), not params (URL parameters).

Your request should look like this on the wire –

POST /TENANT_ID/oauth2/token HTTP/1.1
Host: localhost:9000
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 151
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=MY+CLIENT+ID
&redirect_uri=MY+URL
&resource=https%3A%2F%2Fwestus2.api.loganalytics.io
&client_secret=CLIENT+SECRET

Everything is in the body where it should be for x-www-form-urlencoded.

More on x-www-form-urlencoded here –
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#Example

Answered By: evilSnobu

Here is my solution.

    async getToken() {

        try {

            const url = `https://login.microsoftonline.com/${this.azAdTenant}/oauth2/token`;
            const formFlat = `grant_type=client_credentials&client_id=${this.azAdClientId}&client_secret=${this.azAdClientSecret}&resource=${this.simpleStorageResource}`;

            const response = await this.http.request({
                    baseURL: url,
                    method: "POST",
                    data: formFlat,
                    headers: {'content-type': 'application/x-www-form-urlencoded'},
                }
            ).toPromise();

            return response.data;


        } catch (e) {
            return e;
        }
    }
Answered By: Miguel Plazas