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.
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
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;
}
}
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.
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
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;
}
}