Sorry I am at a bit of a loss trying to set up the graph API.
My end goal is app only access.
When I try to auth I get the error 'access token validation fail. Invalid audience.
My code
import requests
tenant = 'widgetsinc'
token_request_url = 'https://login.microsoftonline.com/{}/oauth2/v2.0/token'.format(tenant)
auth_payload = {
'client_id': 'deaf-beef-cafe',
'scope': 'api://deaf-beef-cafe/.default',
'client_secret': 'hunter2',
'grant_type': 'client_credentials'
}
x = requests.post(token_request_url, data=auth_payload)
token = x.json()
print(token)
url = "https://graph.microsoft.com/v1.0/users"
payload={'Authorization':token['access_token']}
x = requests.post(url, headers=payload)
print(x.json())
exit()```
The result of print(token)
{
'token_type': 'Bearer',
'expires_in': 3599,
'ext_expires_in': 3599,
'access_token': 'REMOVED'
}
the result of print(x.json())
{
'error': {
'code': 'InvalidAuthenticationToken',
'message': 'Access token validation failure. Invalid audience.',
'innerError': {
'date': '2023-01-25T00:38:48',
'request-id': 'removed',
'client-request-id': 'this also removed'
}
}
}```
my permissions for my app in the web ui
permissions
{ 'error': { 'code': 'InvalidAuthenticationToken', 'message': 'Access token validation failure. Invalid audience.', 'innerError': { 'date':'2023-01-25T00:38:48', 'request-id': 'removed', 'client-request-id': 'this also removed' } } }
The above error occurs when you pass the incorrect scope, or your token has the wrong audience, to call the Microsoft Graph API in your environment. According to MS-Document, to get an access token you need pass https://graph.microsoft.com/.default in scope.
I tried with same code using scope https://graph.microsoft.com/.default with same api permission to get all user got an error:
Api permission:
Code:
import requests
tenant = '<tenant-id>'
token_request_url = 'https://login.microsoftonline.com/{}/oauth2/v2.0/token'.format(tenant)
auth_payload = {
'client_id': '<client-id>',
'scope': 'https://graph.microsoft.com/.default',
'client_secret': '<client secret>',
'grant_type': 'client_credentials'
}
x = requests.post(token_request_url, data=auth_payload)
token = x.json()
print(token)
url = "https://graph.microsoft.com/v1.0/users"
payload={'Authorization':token['access_token']}
x1 = requests.get(url, headers=payload)
token1=x1.json()
print(token1)
Console:
To get all user you need to use User.read.all with application API permisson.
API permission:
Console:
After adding API permission to the application, the code executed successfully with all user.
Reference:
List users - Microsoft Graph v1.0 | Microsoft Learn
Related
I am working on an application that needs to obtain a bearer token form Azure AD. Right now I have everything set in Azure and I can generate/obtain the token using Postman (see picture)
However, and this is my struggle, I am having problems obtaining the token programmatically using Python. The code below is what I have tried, with some variations of what to iinclude as fields in the request_payload, without any luck
import json
import requests
#TOKEN_URL = "https://login.microsoftonline.com/organizations/oauth2/v2.0/token"
TOKEN_URL = "https://login.microsoftonline.com/41ff26dc-250f/oauth2/token?resource=https://graph.windows.net"
RESOURCE_URL = "https://login.microsoftonline.com/41ff26dc-739be8610c21/oauth2/authorize?resource=https://graph.windows.net"
def authenticate():
request_payload = {
"callback_url" : "https://localhost",
"auth_url" : "https://login.microsoftonline.com/41ff26dc-250fc21/oauth2/authorize?resource=https://graph.windows.net",
"access_token_url" : "https://login.microsoftonline.com/41ff26dc-739be8610c21/oauth2/token?resource=https://graph.windows.net",
"username": MY USER NAME,
"password": MY PASSWORD,
"resource": RESOURCE_URL,
"grant_type": "Authorization_Code",
"client_id": 'e0d00a8e-b799-4285-be3f-eb5822aaa86e',
"client_secret": '-n24Y2is~p5Jk7~6kYcp4~q2lrmnRCXoW_'}
response = requests.post(url=TOKEN_URL, data=request_payload).json()
print(response)
bearer_token = response["access_token"]
print(bearer_token)
return bearer_token
print(authenticate())
This is the error I get when I fill in my credentials (MY USER NAME and MY PASSWORD) appropriately
{'error': 'invalid_request', 'error_description': "AADSTS900144: The request body must contain the following parameter: 'code'.\r\nTrace ID: d84b06a7-1c45-4657-bb3f-085248de5d01\r\nCorrelation ID: e431a09a-07dc-4c12-bf19-3b8ff7e7c358\r\nTimestamp: 2020-06-25 21:55:25Z", 'error_codes': [900144], 'timestamp': '2020-06-25 21:55:25Z', 'trace_id': 'd84b06a7-1c45-4657-bb3f-085248de5d01', 'correlation_id': 'e431a09a-07dc-4c12-bf19-3b8ff7e7c358', 'error_uri': 'https://login.microsoftonline.com/error?code=900144'}
Any thoughts, suggestions or leads are very much appreciated. Like I said before, I am not sure what fields should I put in the payload, I am not even sure what are all possible fields to try there either.
Please follow this document for auth code flow
you need to send below
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=openid%20offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
&state=12345
please refer to this python sample
I am attempting to use Yahoo's API for fantasy football. I am able to receive an access token and refresh token initially, but once that access token has expired, I am unable to get another one.
My code is as follows:
from requests import Request, get, post
import webbrowser
import base64
baseURL = 'https://api.login.yahoo.com/'
oauthENDPOINT = "https://api.login.yahoo.com/oauth2/request_auth"
## Generate a url using the endpoint and parameters above
params = {'client_id' : client_id,
'redirect_uri' : "oob",
'response_type' : 'code'}
p = Request('GET', oauthENDPOINT, params=params).prepare()
webbrowser.open(p.url)
The last line sends me to the Yahoo website where I allow myself access and receive authCode.
encoded = base64.b64encode((client_id + ':' + client_secret).encode("utf-8"))
headers = {
'Authorization': f'Basic {encoded.decode("utf-8")}',
'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
'grant_type': 'authorization_code',
'redirect_uri': 'oob',
'code': authCode}
tokenResponse = post(baseURL + 'oauth2/get_token', headers=headers, data=data)
tokenResponseJSON = tokenResponse.json()
access_token = tokenResponseJSON['access_token']
refresh_token = tokenResponseJSON['refresh_token']
I now have all the information necessary to examine the settings of my league (for example).
fbURL = 'https://fantasysports.yahooapis.com/fantasy/v2'
leagueURL1 = f'{fbURL}/leagues;league_keys=nfl.l.{leagueID}/settings'
headers = {
'Authorization': f'Bearer {access_token}',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
response2 = get(leagueURL1, headers=headers,params={'format': 'json'})
The above works as expected. However, the access_token lasts for 3600 seconds and once that time has expired I am unable to request a new one, using my refresh_token. My attempt:
accessTokenData = {
'grant_type': 'refresh_token',
'redirect_uri': 'oob',
'code': authCode,
'refresh_token': refresh_token
}
accessTokenResponse = post(baseURL + 'oauth2/get_token', headers=headers, data=accessTokenData)
accessTokenJSON = accessTokenResponse.json()
In the above, I am hoping to receive a new access_token, but instead accessTokenJSON is this:
{'error': {'localizedMessage': 'client request is not acceptable or not supported',
'errorId': 'INVALID_INPUT',
'message': 'client request is not acceptable or not supported'}}
Up to this point I have been following these steps, which worked well up to this point. What am I doing wrong? I understand many Python users use yahoo_oauth or rauth for authentication, but that involves saving the client_id and client_secret in a .json file separately and I'm looking to load those in dynamically. I don't think I'm very far away from succeeding, but I'm just missing something when it comes to generating a new refresh_token. All help much appreciated!
Thanks to referring back to our guide.
Managed to reproduce your error and it's really simple to solve.
You are redefining the headers variable in your request to the fantasyspot url.
The headers variable should be the same in the call for requesting a new access_token using the refresh_token as it was when initially getting both tokens using the auth_code.
So just define header before making requesting a new access_token. Should look like the the following:
headers = {
'Authorization': f'Basic {encoded.decode("utf-8")}',
'Content-Type': 'application/x-www-form-urlencoded'
}
response = post(base_url + 'oauth2/get_token', headers=headers, data=data)
Should work now.
Recommend using different variable names for the headers used for getting an access_token and the one used to the fantasy sport url.
I have deployed and configured Azure API for FHIR using this link - https://learn.microsoft.com/en-gb/azure/healthcare-apis/tutorial-web-app-fhir-server
Using postman i am able to successfully insert a patient information into fhir-server.
To automate it I am using python and client service flow.
def get_access_token(self):
token_url = 'https://login.microsoftonline.com/{}/oauth2/v2.0/token'.format(azure_app_tenant_id)
token_data = {
'grant_type': 'client_credentials',
'client_id': azure_app_client_id,
'client_secret': azure_app_client_secret,
'scope': fhir_endpoint_url + "/.default",
}
token_r = requests.post(token_url, data=token_data)
log.info("Retrieving Access Token")
if token_r.status_code == 200:
log.info("Access Token Retrieved Successfully")
else:
raise Exception("Error retrieving access token")
print(token_r.json()["access_token"])
return token_r.json()["access_token"]
i am able to get an access token using get_access_token. However, when i use the access_token and insert patient record, its throwing Authorization Failed - 403 error.
def insert_patient_record(self, payload):
log.info("Inserting Patient Record")
headers = {
'Authorization': 'Bearer {}'.format(self.get_access_token()),
'Content-Type': 'application/json'
}
response = requests.request("POST", fhir_endpoint_url, headers=headers, data=payload)
print("Response Code: ", response.status_code)
if response.status_code == 200:
log.info("Patient Record inserted Successfully")
else:
print("Response Text: ", response.text)
raise Exception("Error inserting patient record")
Response Text: {"resourceType":"OperationOutcome","id":"24515888da8e954da1e763d96193155b","issue":[{"severity":"error","code":"forbidden","diagnostics":"Authorization failed."}]}
Note: In FHIR-Server Authentication section, i have added the Object ID of the Registered APP which i earlier created in ADD.
It looks like you have not added the (correct) object id of the registered application. Importantly, the application registration has an object id, but so does the service principal. It is the application id for the service principal you are looking for.
Check instructions here:
https://learn.microsoft.com/en-us/azure/healthcare-apis/find-identity-object-ids
You can find it the service principal object id with PowerShell:
$(Get-AzureADServicePrincipal -Filter "AppId eq 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'").ObjectId
or the Azure CLI:
az ad sp show --id XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX | jq -r .objectId
I would also recommend pasting your token into something like https://jwt.ms and look at the oid claim. Is that the object id you added?
I'm trying to figure out how to initiate a push notification using DialogFlow/Google Actions. I've cobbled together some test code, but when I test it, I get a '403 Forbidden' response. Can somebody provide me a good example of how to do this using Python 3.
`
import urllib.request
import json
notification = {
'userNotification': {
'title': 'Test Notification',
},
'target': {
'userId': 'user-id here',
'intent': 'Notification Intent',
'locale': 'en-US',
}
}
my_url = 'https://actions.googleapis.com/v2/conversations:send'
access_token = 'access token here'
request = urllib.request.Request(my_url)
request.add_header('Content-Type', 'application/json; charset=utf-8')
payload = { 'auth': { 'bearer': access_token },
'json': 'true',
'body': { 'customPushMessage': notification, 'isInSandbox':
'true' } };
jsondata = json.dumps(payload)
jsondataasbytes = jsondata.encode('utf-8')
response = urllib.request.urlopen(request, jsondataasbytes)
`
Can anybody provide any suggestions about how to get this to work?
=============================================================
Update: I revised the auth header as suggested and now I'm getting '401:Unauthorized'. I'm not sure if I'm creating the access token properly. Here's how I'm doing it:
I created an RSA256 private key on the Google Console website. I use that key to encode a JWT containing these fields:
{
"iss": [
"My Service Name",
"\"my_service-account#service_name.iam.gserviceaccount.com\""
],
"iat": 1544018697,
"exp": 1544019898,
"aud":
"https://www.googleapis.com/auth/actions.fulfillment.conversation\"",
"sub": [
"Push Notification",
"\"my-service-account#service_name.iam.gserviceaccount.com\""
]
}
I don't know if this is correct: the documentation for all of this is hard to pull together.
UPDATE 2:
I modified the code suggested by Prisoner, and I'm now able to get what appears to be a valid access_token. This is the modified code:
from oauth2client.client import GoogleCredentials
service_account_file = 'path-to-service_account_file'
credentials = GoogleCredentials.from_stream(SERVICE_ACCOUNT_FILE)
access_token = credentials.get_access_token().access_token
When I try to use the access token, I'm still getting a '401 Unauthorized' response. Has anybody actually done this? If so, can you give me some specifics on the correct URL to use and how to format the urllib.request message?
You've placed some things into the body of the request that belong in the header. In particular, the "403 forbidden" suggests that the Authorization header is either wrong or missing, and in your case, it looks like it is missing since you're trying to put it in the body.
The body of what you're sending should just contain the JSON with the customPushMessage attribute.
I'm not very familiar with python, but I think something like this is more what you want:
request = urllib.request.Request(my_url)
request.add_header('Authorization', 'bearer '+access_token)
request.add_header('Content-Type', 'application/json; charset=utf-8')
payload = { 'customPushMessage': notification }
jsondata = json.dumps(payload)
jsondataasbytes = jsondata.encode('utf-8')
response = urllib.request.urlopen(request, jsondataasbytes)
If you continue to get the "403 Forbidden" message - make sure your access_token is current and is actually an access token. Access tokens are created from the service account key, but are not the key itself. They have a limited lifetime (usually 1 hour), while the key is long-lasting.
Update about generating an access token from the keys.
You can't just create and sign a JWT to use as an access token.
The easiest way is to use the Google APIs Client Library for Python, which includes a library to handle OAuth with service accounts.
I haven't tested, but you should be able to do something like this, setting SERVICE_ACCOUNT_FILE to the location of where the keys are stored.
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/actions.fulfillment.conversation']
SERVICE_ACCOUNT_FILE = '/path/to/service.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
access_token = credentials.get_access_token()
Ok, I've figured out how to get the the access_token, so that part of the problem is solved. I've run into another problem getting updates permission working for my test account, but I'm going to post a new question to cover that. I adapted code from another answer and this seems to work:
# Adapted from https://stackoverflow.com/questions/51821919/how-to-send-push-notification-from-google-assistant-through-dialogflow-fulfilmen/51961677#51961677
import io
import json
import requests
from google.oauth2 import service_account
import google.auth.transport.requests
def send_notification(path_to_service_account="default", intent="default"):
if path_to_service_account == "default":
PATH_TO_SERVICE_ACCOUNT = 'path to downloaded service account json file from Google Cloud Console'
else:
PATH_TO_SERVICE_ACCOUNT = path_to_service_account
if intent == "default":
INTENT = 'Notification Intent'
else:
INTENT = intent
REQUIRED_SCOPE = 'https://www.googleapis.com/auth/actions.fulfillment.conversation'
USER_ID = 'user id here'
INTENT = 'Your intent name'
# Get access token
with io.open(PATH_TO_SERVICE_ACCOUNT, 'r', encoding='utf-8') as json_fi:
credentials_info = json.load(json_fi)
credentials = service_account.Credentials.from_service_account_info(
credentials_info, scopes=[REQUIRED_SCOPE])
request = google.auth.transport.requests.Request()
credentials.refresh(request)
headers = {
'Authorization': 'Bearer ' + credentials.token
}
payload = {
'customPushMessage': {
'userNotification': {
'title': 'App Title',
'text': 'Simple Text'
},
'target': {
'userId': USER_ID,
'intent': INTENT,
# Expects a IETF BCP-47 language code (i.e. en-US)
'locale': 'en-US'
}
}
}
r = requests.request("POST", 'https://actions.googleapis.com/v2/conversations:send', data=json.dumps(payload), headers=headers)
print(str(r.status_code) + ': ' + r.text)
I have the following Python code to authenticate against the Graph API
import requests
def login(tenant_name, client_id, client_secret, username, password):
url = 'https://login.windows.net/' + tenant_name + '/oauth2/token'
payload = {
'grant_type': 'password',
'username': username + '#' + tenant_name,
'password': password,
'client_id': client_id,
'client_secret': client_secret,
'resource': 'https://graph.windows.net'
}
r = requests.post(url, data=payload)
return r.json()
If I have user, whose password has expired, I get a response(as expected):
{
'timestamp': '2015-09-15 02:59:26Z',
'trace_id': '8abff845-6941-4867-9729-15626c23330f',
'submit_url': None,
'correlation_id': '81184c06-2627-4bca-82e3-76aab7713a5f',
'error_description': 'AADSTS70002: Error validating credentials. AADSTS50055: Password is expired.
Trace ID: 8abff845-6941-4867-9729-15626c23330f
Correlation ID: 81184c06-2627-4bca-82e3-76aab7713a5f
Timestamp: 2015-09-15 02:59:26Z',
'context': None,
'error': 'user_password_expired',
'error_codes': [70002, 50055]
}
Graph API provides use an endpoint to reset a password, but to do so, I need a valid token (to make a PATCH request to the endpoint mentioned in the documentation) and since I couldn't login, I don't have one.
What is the right way to change a user password on expiry using the Azure Graph API?
You can't.
At least not with the Resource Owner Password Credentials Grant (grant_type=password) flow, where you only have the end-user's credentials (though really, there are very few cases where this flow is a good choice--see this answer and this answer for more).
The user needs to be directed to Azure AD's web interface (in a browser/web view) to authenticate and change their password.