I am playing with Microsoft Graph to access Azure Active Directory from my application, using the REST API directly (without an SDK).
According to the documentation, I should be able to retrieve a user from their id or userPrincipalName using /users/{id | userPrincipalName}.
This is indeed working, but not for Guest users. With Guest users the userPrincipalName is something like name_originaldomain#EXT##mydomain.onmicrosoft.com, and trying to get the user results in a 404 Not Found.
This is the code I am currently using:
graphClient = new HttpClient();
graphClient.BaseAddress = new Uri("https://graph.microsoft.com/v1.0/");
graphClient.DefaultRequestHeaders.Add("Authorization", $"{token.TokenType} {token.AccessToken}");
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Get,
$"users/{Uri.EscapeUriString(username)}"
);
HttpResponseMessage response = await graphClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<UserResult>();
}
// I am here with a 404
Am I missing something or doing something wrong?
An alternative is to create a bigger query that also queries on the mail property with the guest user's email address (or in your case what you have as "name#originaldomain"). For guest users, we set the mail property to the guest's email address.
../users?$filter=mail eq 'name#originaldomain'
Hope this helps,
You need to URL Encode the userPrincipalName. Otherwise you're effectively passing name_originaldomain since the # designates everything beyond that point as a URI Fragment.
Try using name_originaldomain%23EXT%23%40mydomain.onmicrosoft.com
Related
I am integrating a Smart on FHIR app that will be launched from within an EHR. When the user clicks a button to launch the app, we set a GUID and the current Patient ID to a database record on our FHIR server. The assumption being that given the 'Launch' scope, the OAuth server will call the appropriate API to retrieve the Patient ID given that the GUID is included in the url params.
The call to auth looks like this:
_clientID = {the unique client ID registered to our auth server}
_redirectURL = {redirect back to auth for eventual token request}
launch={the GUID value generated at start of the session and paired with the Patient ID}
_scopes = "launch patient/*.* openid profile"
state = {some opaque value}
aud = {the base URL for our FHIR server}
string url = $"{authorizeURL}?response_type=code&client_id={_clientID}&" +
$"redirect_uri={_redirectURL}&" +
$"launch={launch}&" +
$"scope={HttpUtility.UrlEncode(_scopes)}&" +
$"state={state}&" +
$"aud=https://xxx-smart.xxxxxxxxx.com";
All of this works and I end up with a json response that includes the id_token, access_token, expires_in, token_type('Bearer'). But, no 'patient'.
My assumption was that the OAuth server would call the scope 'launch/patient' on our FHIR server but no such call is being made. In fact, I created a few endpoints just for the purpose of logging and NONE of them are being called.
Here is an example of one of my FHIR Server test/log endpoints (I created few with 1 to 4 parameters):
[AllowAnonymous]
[HttpGet("{functionName}/{id}")]
public string GetPatientData3(string functionName, string id)
{
TelemetryClient telemetry = new();
telemetry.TrackEvent($"FHIR SVR GetPatientData3 {functionName} {id}");
string configJson = "0009998888";
return configJson;
}
How do I set this 'patient' context properly?
How does the OAuth server retrieve this context so I can have that patient ID appear in the json response from the ~/token call?
Further Notes:
The contents of the openid-configuration:
{"token_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/token",
"token_endpoint_auth_methods_supported":
["client_secret_post","private_key_jwt","client_secret_basic"],
"jwks_uri":
"https://login.microsoftonline.com/xxx/discovery/v2.0/keys",
"response_modes_supported": ["query","fragment","form_post"],
"subject_types_supported": ["pairwise"],
"id_token_signing_alg_values_supported": ["RS256"],
"response_types_supported":["code","id_token","code
id_token","id_token token"],
"scopes_supported":["openid","profile","email","offline_access"],
"issuer": "https://login.microsoftonline.com/xxx/v2.0",
"request_uri_parameter_supported":false,
"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo",
"authorization_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/authorize",
"device_authorization_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/devicecode",
"http_logout_supported":true,
"frontchannel_logout_supported":true,
"end_session_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/logout",
"claims_supported":
["sub","iss","cloud_instance_name","cloud_instance_host_name",
"cloud_graph_host_name","msgraph_host","aud","exp","iat",
"auth_time","acr","nonce","preferred_username",
"name","tid","ver","at_hash","c_hash","email"],
"kerberos_endpoint":
"https://login.microsoftonline.com/xxx/kerberos",
"tenant_region_scope":"NA",
"cloud_instance_name":"microsoftonline.com",
"cloud_graph_host_name":"graph.windows.net",
"msgraph_host":"graph.microsoft.com",
"rbac_url":"https://pas.windows.net"}
So, I notice that the 'patient/.' and the 'launch' scope among a whole host of others that I have are not supported according to my openid config. The only supported ones are "openid","profile","email", "offline_access".
In Azure AD, 'App Registration' > 'Expose an API' I have a list of at least 15 scopes entered there. In 'API' permissions they are all listed there as well.
One other thing to note, AzureAD does not handle scopes with a forward slash. So, launch/patient has to be entered as launch-patient. We also had to implement a proxy server to capture the ~/oauth2/v2.0/authorize request and modify the scope parameter entries to reflect this before passing on the request to the actual server.
I guess the pertinent question now is: How do the scopes that I have entered manually get supported?
We are trying to query shifts in the Microsoft Graph API using a C# app, now that StaffHub got deprecated , in the past we were getting an Unknown Error which looked like a permissions issue.
In the docs I noticed permissions for Schedule.ReadAll and Schedule.ReadWriteAll so I added them to the application permissions in our App Registration in Azure.
Now when we send the request to https://graph.microsoft.com/beta/teams/{teamid}/schedule we get this error:
Microsoft.Graph.ServiceException: 'Code: Forbidden Message: {"error":{"code":"Forbidden","message":"MS-APP-ACTS-AS header needs to be set for application context requests.","details":[],"innererror":{"code":"MissingUserIdHeaderInAppContext"}}}
The documentation says the Schedule permissions are in private preview, are these required for querying a schedule & shifts, and if so, is it possible to request access to the private preview?
I'm in the same situation. It's possible to request private preview access (we have), but I'm guessing that it's primarily granted to Microsoft partners or at least have a connection at Microsoft.
The workaround for me has been getting access on behalf of a user. It does however require the user to enter username and password in order to get an access token, so it might not be a perfect solution for you. But it works. You need to add (and, I believe, grant admin consent for) delegated permissions for this to work, either Group.Read.All or Group.ReadWrite.All.
Edit:
I've got it working now. We have private preview access, so I'm not sure this will help you unless you do too, but as I understand it will be available eventually. Given your question, I presume you already have an access token.
Add MS-APP-ACT-AS as a header with the user ID of the user you want the Graph client to act as.
If you're using the Graph SDK for .NET Core you can just add a header to the authentication provider:
public IAuthenticationProvider GetAuthenticationProviderForActingAsUser(string userId, string accessToken)
{
return new DelegateAuthenticationProvider(
requestMessage =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
requestMessage.Headers.Add("MS-APP-ACTS-AS", userId);
return Task.CompletedTask;
});
}
Then you call the graph service client:
var authenticationProvider = GetAuthenticationProviderForActingAsUser(userId, accessToken);
var graphClient = new GraphServiceClient(authenticationProvider);
You should then be able to fetch the shifts:
var shifts = await graphClient.Teams[teamId].Schedule.Shifts
.Request()
.AddAsync(shift);
SUMMARY UPDATE:
I got a sample working today thanks to the many good replies. Thanks all. My primary goal was to get current user information (ME) without using secret key. First I just used the secret key from the App Reg and this will authenticate the App and not the user. This does of course not work when calling ME. My next finding was if you want the users token, you still need the App Reg token, and then you request the users token. This requires less permissions on the App Reg, but requires to request two tokens. I ended up skipping ME and just requesting information for a specified user (through the APp Reg permissions):
$"https://graph.microsoft.com/v1.0/users/{email}/$select=companyName"
Both both approaches should be viable. I updated code below with working sample.
I am trying to do a very simple call to graph API to get companyName from current user. Found some samples but they seemed to be very complicated. The MVC app is authenticated trough an Application Registration in AAD.
I guess the application registration needs to be authorized to access Graph API. Or is more needed here? Getting company name should be fairly simple:
https://graph.microsoft.com/v1.0/me?$select=companyName
Does anyone have a snippet for calling the graph API, my best bet would be you need to extract a bearer token from the controller? ALl help is appreciated.
Working snippet:
public async Task<ActionResult> Index()
{
string clientId = "xxx";
string clientSecret = "xxx";
var email = User.Identity.Name;
AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/xxx.onmicrosoft.com/oauth2/token");
ClientCredential creds = new ClientCredential(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
HttpClient http = new HttpClient();
string url = $"https://graph.microsoft.com/v1.0/users/{email}/$select=companyName";
//url = "https://graph.windows.net/xxx.onmicrosoft.com/users?api-version=1.6";
// Append the access token for the Graph API to the Authorization header of the request by using the Bearer scheme.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
HttpResponseMessage response = await http.SendAsync(request);
var json = response.Content.ReadAsStringAsync();
return View();
}
To add one last item, here is a link to an MVC sample on Git that uses an MVC application to send email. It illustrates how to call the MS Graph API to get various pieces of information. Keep in mind, if you are using an application only scenario, ME will not work, the sample illustrates how to obtain a delegated token for a user and use that toke to do work:
https://github.com/microsoftgraph/aspnet-connect-rest-sample
If I am reading this code snippet correctly, You are requesting a application only token for the Graph.Microsoft.Com resource, then attempting to use that toke with this URI:
url = "https://graph.windows.net/thomaseg.onmicrosoft.com/users?api-version=1.6"
This will not work because you are mixing resources, AAD Graph and MS Graph. The ME endpoint does not make since in this scenario because you are using the application only flow. This flow does not support the ME endpoint. ME is designed for use with a delegated token. the ME endpoint represents the signed in user, since and application is not a user, ME is meaningless.
You will need to target the user specifically:
https://graph.microsoft.com/v1.0/Users/[UPN or ID of user]?$select=companyName
Should work if your application has been granted the appropriate permission scopes.
I am trying to use Microsoft Graph API to update another user in Active Directory.
I have the following permissions set for both user and application at https://apps.dev.microsoft.com/
I've requested the following scopes:
Directory.ReadWrite.All
User.ReadWrite.All
Group.ReadWrite.All
I am able to get a listing of all users in the directory, but when trying to update (in this case, the city) it fails:
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient ();
var usersResponse = graphClient.Users.Request ().GetAsync ();
var users = usersResponse.Result;
// hard coding user id for now
var userId = "9a5b83cd-85ff-4ad1-ab2f-b443941a518e";
var user = users.FirstOrDefault (m => m.Id == userId);
if (user != null) {
user.City = "New York";
await graphClient.Me.Request ().UpdateAsync (user);
}
I get:
{
Code : Authorization_RequestDenied
Message : Insufficient privileges to complete the operation.
Inner error
}
The user I am logged in as is a Global Administrator of the directory.
I took the JWT token, headed over to https://jwt.io and these are the roles I am seeing:
Directory.Read.All
Directory.ReadWrite.All
Files.ReadWrite
Group.ReadWrite.All
Mail.Send
User.Read
User.Read.All
User.ReadWrite.All
Do I need other permissions to make this happen?
At the end of the day, I'd like to create a console app (not web app) that I can update other user information in the directory. But I figured using this sample app provided by Microsoft is a good start.
The reason you're seeing this is because you're passing the complete user object rather than only the city property. In other words, you're attempting to update every property in that user record, including several that are read-only.
This is one of those cases where having an SDK that wraps a REST API can be result in some confusing errors. As REST API, it is stateless so passing in the entire user property set is telling the API you want to PATCH all of those values.
You're also passing in a different user object into the me object (i.e. you're replacing all of your property values with this other user's property values):
await graphClient.Me.Request().UpdateAsync(user);
Instead, try this:
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient ();
// hard coding user id for now
var userId = "9a5b83cd-85ff-4ad1-ab2f-b443941a518e";
await graphClient.Users[userId].Request ().UpdateAsync(new User
{
City = "New York"
});
My windows service is collect instagram datas from instagram api. I was using client_id but this uses format is removed.
Instagram api is want to access_token but Oauth 2.0 is web-based. or not?
I using .NET and my application type is windows service and web request don't work because this call url: "https://www.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=code" is have one more contain redirect. so web response haven't contain my web application link also auto redirect is open..
what should I do?
Thanks..
Steps to get instagram access token
register ur application in instagram account.
get a client id and client secret.
Step 1: HIT the below url.
https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=code
step 2: after hitting above url you will be taken to login page. enter the login credentials and take the code from address bar.
it will be live for only 20 seconds i guess.
step 3: The code which you got put it in CODE parameter in the below source code, then run the below code in console application n hit breakpoint at response. you will get access token and userid.
public void GetDataInstagramToken()
{
try
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add("client_id", "CLIENT-ID");
parameters.Add("client_secret", "CLIENT-Secret");
parameters.Add("grant_type", "authorization_code");
parameters.Add("redirect_uri", "REDIRECT-URI");
parameters.Add("code", "CODE");
WebClient client = new WebClient();
var result = client.UploadValues("https://api.instagram.com/oauth/access_token", "POST", parameters);
var response = System.Text.Encoding.Default.GetString(result);
// deserializing nested JSON string to object
var jsResult = (JObject)JsonConvert.DeserializeObject(response);
string accessToken = (string)jsResult["access_token"];
}
catch (Exception)
{
//exception catch
}
}