I want to be able to search for users across multiple tenants, and therefore my thoughts were to create a python script that runs on HTTP triggered Azure functions. This python script can authenticate to Microsoft Graph API for different tenants via service principals and then search for a user and return the data. is this a good idea or is there a better way of doing this?
Let's discuss on the achievement.
I find that one multi-tenant azure ad application is enough for querying users in different tenant through graph api. For example, there're 2 tenants, I created a multi-tenant application in azure ad app registration, after that I generated the client secret and add api permission of User.Read.All.
Now I have an app with its client id and secret in 'tenant_a'. Next, visit https://login.microsoftonline.com/{tenant_b}/adminconsent?client_id={client-id} in the browser, after sign in with the admin account in tenant_b, it will appear a 'permission' window to make consent the application have permission in tenant_b, after the consent, you will the the app created in tenant_a appears in the list of Enterprise applications in tenant_b.
Now we need to generate access token for different tenant to call graph api. It's necessary to generate access token for each tenant, because I tried to use common to replace the domain in the request(https://login.microsoftonline.com/common/oauth2/v2.0/token), it can generate access token successfully, but the token can't used in the api to query user information. The query user api needs user principal name as the input parameter. For example, I have a user which account is 'bob#tenant_b.onmicrosoft.com', use the account as the parameter is ok to get response, but if I use 'bob' as the parameter, it will return 'Resource xxx does not exist...'.
I'm not an expert in python, I only found a sample and tested successfully with it. Here's my code, it will execute loop query until the user be found. And if you wanna a function, you may create a http trigger base on it.
import sys
import json
import logging
import requests
import msal
config = json.load(open(sys.argv[1]))
authorityName = ["<tenant_a>.onmicrosoft.com","<tenant_b>.onmicrosoft.com"]
username = "userone#<tenant_a>.onmicrosoft.com"
for domainName in authorityName:
# Create a preferably long-lived app instance which maintains a token cache.
print("==============:"+config["authority"]+domainName)
app = msal.ConfidentialClientApplication(
"<client_id>", authority="https://login.microsoftonline.com/"+domainName,
client_credential="<client_secret>",
)
# The pattern to acquire a token looks like this.
result = None
# Firstly, looks up a token from cache
# Since we are looking for token for the current app, NOT for an end user,
# notice we give account parameter as None.
result = app.acquire_token_silent(["https://graph.microsoft.com/.default"], account=None)
if not result:
result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
if "access_token" in result:
print("access token===:"+result['access_token'])
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
"https://graph.microsoft.com/v1.0/users/"+username,
headers={'Authorization': 'Bearer ' + result['access_token']}, ).json()
if "error" in graph_data:
print("error===="+json.dumps(graph_data, indent=2))
else:
print(json.dumps(graph_data, indent=2))
break
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
Related
I want, when user login he should get list of tenant, from that list, user decide in which tenant he want to redirect
I want to call https://graph.microsoft.com/v1.0/organization API but when I write code to call it , it will returned error, I have get token using below code, it is worked for users API of Graph, but not working for organization api
B2BGraphClient.AccessToken = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", credential).ConfigureAwait(false);
I have checked it using Postman, when I have pass token generated using https://login.microsoftonline.com/common/oauth2/token this api, organization api returns correct output, but logically it is not possible in code to pass userid and password to api and get token, below is image of postman call
I want correct way to do this
Your code is using Client_credentials flow but in Postman you are using ROPC flow.
The two flows use different permission type. Client_credentials flow uses Application permission while ROPC flow uses Delegated permission.
So for Client_credentials flow, if the app belongs to a work or school (organization) context then for https://login.microsoftonline.com/common/oauth2/token replace common with a tenantId or domain name. If you don't do this, you will get the Authorization_IdentityNotFound error.
Specify the tenant in the code (modify the sample code based on your needs).
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenant = tenant;
// The AuthenticationContext is ADAL's primary class, in which you indicate the direcotry to use.
this.authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenant);
// The ClientCredential is where you pass in your client_id and client_secret, which are provided to Azure AD in order to receive an access_token using the app's identity.
this.credential = new ClientCredential(clientId, clientSecret);
B2BGraphClient.AccessToken = await this.authContext.AcquireTokenAsync("https://graph.microsoft.com/", this.credential).ConfigureAwait(false);
And don't forget to add one of the following Application permissions Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All in your app registration as per Permissions.
UPDATE:
In fact this endpoint https://graph.microsoft.com/v1.0/organization can't return the tenants which the user is member of.
You have found the correct Azure rest API to list the tenants.
But this API also doesn't support client_credentials flow.
In another word, you cannot use authContext.AcquireTokenAsync to get the token. You should consider AcquireTokenByAuthorizationCodeAsync. And specify the scope as https://management.azure.com/ instead of https://graph.microsoft.com.
Don't forget to add the Azure Rest permission in app registration.
I have a working azure authentication layer set to a flask app using flask dance make_azure_blueprint.
blueprint = make_azure_blueprint(
client_id=client_id,
client_secret=client_secret,
tenant=tenant_id,
scope=[
scopes.Email,
scopes.DirectoryReadAll,
scopes.OpenID,
scopes.Profile,
scopes.UserRead,
scopes.UserReadAll,
],
login_url=LOGIN_URL_PATH,
authorized_url=AUTH_CALLBACK_URL_PATH,
redirect_url='http://localhost:5000/',
)
app.register_blueprint(blueprint, url_prefix="/login")
where the scopes are :
scopes -
DirectoryReadAll = 'Directory.Read.All'
Email = 'email'
GroupMemberReadAll = 'GroupMember.Read.All'
Profile = 'profile'
OpenID = 'openid'
UserReadBasicAll = 'User.ReadBasic.All'
UserRead = 'User.Read'
UserReadAll = 'User.Read.All'
using this I was able to retrieve the user information and display on the app. Now I am trying to combine Azure Time series insights scope "https://api.timeseries.azure.com//user_impersonation". But this is returning an error saying that this cannot be mixed with resource specific groups.
enter image description here
Your needs are unreachable.
It seems you try to access two apis both default scope and user_impersonation scope. Actually we cannot use multiple scopes to access apis.
You should put the api you want to access in the scope. For example, if you want to access MS graph api, you can put https://graph.microsoft.com/.default. If you want to access a custom api, you can put in api://{back-end app client api}/scope name.
I am trying to automate creating tickets in Salesforce. For this, I am using API with Python. I have got the Client ID and Client secret for my registered python Application. I have read many questions and as per the security purpose, I do not want to use the "user-password" flow for my script. Is there any way that I can only use "CLIENT ID" and "CLIENT SECRET" to get the access token where I can pass this access token in bearer header for other calls
import requests
params = {
"grant_type": "client_credentials",
"client_id": client_id, # Consumer Key
"client_secret": client_secret, # Consumer Secret
}
r=requests.post("https://login.salesforce.com/services/oauth2/token", params=params)
access_token = r.json().get("access_token")
instance_url = r.json().get("instance_url")
print("Access Token:", access_token)
You'll always need a SF user account. There's no way to just make a backend system talk to SF system. Salesforce treats everybody as user so you need to waste an account for "integration user" - but in return you can control access to tables, columns, functionalities just like you control real humans' access. This goes all the way down to the underlying Oracle database and database user privileges.
Whether you use OAuth2 flows (including client secrets) or maybe some certificate-based authentication - there will be always some element of "username and password" required. Best you can do is to make sure your app doesn't need to see & store the password, instead showing normal SF login prompt and on successful login user is redirected to your app to continue with known session id...
There might be something you'll be able to automate more if your app and SF use same Single Sign-On but broadly speaking... You have to either let users login to SF via your app or create the tickets as some dedicated admin user (and then you store this user's credentials in your app)
In an old application some people in my company were able to get info from Microsoft Graph without signing users in. I've tried to replicate this but I get unauthorized when trying to fetch users. I think the graph might have changed, or I'm doing something wrong in Azure when I register my app.
So in the Azure portal i have registered an application (web app), and granted it permissions to Azure ad and Microsoft graph to read all users full profiles.
Then I do a request
var client = new RestClient(string.Format("https://login.microsoftonline.com/{0}/oauth2/token", _tenant));
var request = new RestRequest();
request.Method = Method.POST;
request.AddParameter("tenant", _tenant);
request.AddParameter("client_id", _clientId);
request.AddParameter("client_secret", _secret);
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("resource", "https://graph.microsoft.com");
request.AddParameter("scope", "Directory.Read.All");
I added the last row (scope) while testing. I still got a token without this but the result is same with or without it.
After I get a token I save it and do this request:
var testClient = new RestClient(string.Format("https://graph.microsoft.com/v1.0/users/{0}", "test#test.onmicrosoft.com")); //I use a real user here in my code ofc.
testRequest = new RestRequest();
testRequest.Method = Method.GET;
testRequest.AddParameter("Authorization", _token.Token);
var testResponse = testClient.Execute(testRequest);
However now I get an error saying unauthorized, Bearer access token is empty.
The errors point me to signing users in and doing the request, however I do not want to sign a user in. As far as i know this was possible before. Have Microsoft changed it to not allow anonymous requests?
If so, is it possible to not redirecting the user to a consent-page? The users are already signed in via Owin. However users may have different access and i want this app to be able to access everything from the azure ad, regardless of wich user is logged in. How is the correct way of doing this nowadays?
Or am I just missing something obvious? The app has been given access to azure and microsoft graph and an admin has granted permissions for the app.
Edit: just to clarify, i tried both "Authorization", "bearer " + _token.Token, and just _token.Token as in the snippet.
Yes, it's still possible to make requests to Graph without a user present using application permissions. You will need to have the tenant admin consent and approve your application.
Edit / answer: Adding the 'Authorization' as a header instead of a parameter did the trick. It works both with 'bearer token' and just 'token'
My team and I built Web App that uses the Microsoft Graph API to recover data from a user's Office 365 environment. The application uses Azure AD to provide users with an access token.
We're now trying to add a component that can access the same user's Exchange Online informations using the EWS Api. However, it seems like trying to use the same access token as the one provided by the Graph API always returns a 401 (Unauthorized) response even if I have to proper permissions set on my Azure AD Application. Here is the code we use to try and access some user's information:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.HttpHeaders.Add("Authorization", "Bearer " + accessToken);
service.PreAuthenticate = true;
service.SendClientLatencies = true;
service.EnableScpLookup = false;
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
TasksFolder tasksfolder = TasksFolder.Bind(service, WellKnownFolderName.Tasks,
new PropertySet(BasePropertySet.IdOnly, FolderSchema.TotalCount));
The Bind method will always return a 401 error which prompts me to think that ther access token from one API is not valid for another one.
If that's the case, is it possible to get a single access token that will be valid for mutiple API calls ?
It should work okay but EWS doesn't support the constrained permissions model like REST so " EWS applications require the "Full access to user's mailbox" permission. as per the note in https://msdn.microsoft.com/en-us/library/office/dn903761(v=exchg.150).aspx