Asking for user info anonymously Microsoft Graph - azure

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'

Related

Setting up an Application with Azure for use with Graph API outlook calendars

I'm aware that Graph API has a nice nuget package and I am confident on the code side of things, but my understanding is that I need to have the application set up in Azure and while there is a lot of documentation about this in general, I find it quite dense and I'm not confident I have the specifics down for how I need to set this portion up.
What I need my application to do is access an outlook calendar of a specific user that I own, read, search, add, delete and update calendar items. The integration assistant seems to suggest I need to configure a URI redirect and configure api permission. The default persmission is User.Read on graph API and if I try to add a permission, office 365 management seems like it might be the one I need except it specifically says just retrieving user information and nothing mentions outlook anywhere.
I need to know more or less the minimum steps in setting up the application in Azure to write a C# application that can make changes to outlook for a user.
need my application to do is access an outlook calendar of a specific user
Does it mean you need your app to have the abiltity to modify the callendar of any user you owned? If not, then it means you need your application to provide a sign in module and let users sign in, then the users can get authentication to call graph api and manage their own calendar, since this scenario, users give the delegate api permission, so they can't manage other users' calendar, so I don't think this is what you want.
If so, then you should use client credential flow to generate access token to call graph api. I think you know that when you want to call graph api, you have to get an access token which has correct permission first.
Ok, then let's come to the api permission, when you go to api document of the Calendar. You will see permissions like screenshot below:
Application permission type is suitable for client credential flow. And after viewing all the apis, you will find that they all need Calendars.ReadWrite except those not supporting Application type.
Then let's go to azure portal and reach Azure Active Directory. You need to create an Azure ad application and give the app Calendars.ReadWrite permission, then give the Admin consent.
Then you also need to create a client secret, pls go to Certificates & Secrets and add a new client secret, don't forget to copy the secret after you create it.
Now you've done all the steps. No need to set a redirect url, because you don't need to let the user to sign in your application. Let's see client credential flow document, it only require client_id, client_secret to generate access token.
Or in the code, you may use SDK like this :
using Azure.Identity;
using Microsoft.Graph;
public async Task<IActionResult> Index()
{
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "azure_ad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var calendar = new Calendar{ Name = "Volunteer" };
var events = await graphClient.Users["user_id_which_is_needed_to_list_calendar_events"].Events.Request()
.Header("Prefer","outlook.timezone=\"Pacific Standard Time\"")
.Select("subject,body,bodyPreview,organizer,attendees,start,end,location")
.GetAsync();
return View();
}

Dynamics 365 Business Central Token

I'm attempting to gain access to Business Central Admin Center API, but I'm having some difficulties.
I'm having the idea that it has something to do with the app registration that I have made in the Azure Portal.
I have (as an admin user of the tenant) registered and app and given it "delegated permissions" to "Dynamics 365 Business Central" with access to "Financials.ReadWrite.All".
I have also created a secret for the app.
My problem is that when I try to access the Admin Center API, I get a "403 Forbidden" response, so I assume that I have either forgotten something, I have created my app registration wrong somehow or that my attempt to access the API, is performed in an inaccurate manor.
If I try to examine the token I get, it doesn't show the permissions that I would expect and have seen in other cases (like with MS Graph API), so I'm thinking maybe it's the token that is the problem.
Here is the code that I use to retrieve a token and my attempt to use it afterwards - maybe someone can spot what I'm doing wrong.
Getting the token
var client_id = "removed_for_security_reasons";
var client_secret = "removed_for_security_reasons";
var tenant_id = "removed_for_security_reasons";
var token_url = "https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token";
var client = new HttpClient();
var content = new StringContent(
"grant_type=client_credentials"+
"&scope=https://api.businesscentral.dynamics.com/.default"+
"&client_id="+ HttpUtility.UrlEncode(client_id) +
"&client_secret="+ HttpUtility.UrlEncode(client_secret));
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
var response = await client.PostAsync(token_url, content);
// here i print the token so I can check it with jwt.io.
Attempting to use the token
var client = new HttpClient();
HttpRequestMessage req = new HttpRequestMessage();
req.Method = HttpMethod.Get;
req.RequestUri = new Uri("https://api.businesscentral.dynamics.com/admin/v2.11/applications/businesscentral/environments");
req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", access_token);
var res = await client.SendAsync(req);
// this results in "403 Forbidden"
There is no further information given as to why this is forbidden, so I'm having a hard time pin pointing what the problem is.
Does anyone have suggestions?
UPDATE 1
OK, so I have tried to follow the description linked. It doesn't describe which permissions box to check though and it's also using PowerShell which I'm not - I'm using C# with HttpClient.
So, to not circle around this any further, please try to explain which to select here (see images) and/or what is wrong/missing.
Image 1 (the app), what is wrong/missing:
Image 2 (permissions 1), what is wrong/missing:
Image 3 (permissions 2), what is wrong/missing: (admin grant doesn't seem to change anything)
After this, I create a client secret and use the code posted initially.
Of cause this isn't working as expected. If the code is wrong, then please point out what the problem is - referring to the description on the web doesn't help me, as it is vague at best.
I think the issue is your combination of delegated permissions and trying to use the client credential flow.
Client credential flow requires application permissions which is also why your delegated permissions are not shown in your token. The client credential flow does not grant you the delegated permissions.
Even though it doesn't seem to be stated directly anywhere that Admin Center API doesn't support client credential flow, I think it is implied in the documentation.
In Using Service-to-Service (S2S) Authentication the Admin Center API is not mentioned in the Feature availability matrix and The Business Central Admin Center API does not mention client credential flow at all and all the example are using user impersonation.
Your App Registration looks okay to me. You will however need to provide the admin consent.
As described in the article I linked above you need to use MSAL (Microsoft Authentication Library). Since you are using C# you need to use MSAL.NET.
I am not an expert on C#, but maybe this quickstart guide could lead you in the right direction.

Azure Active Directory Authentication and SharePoint CSOM

I have a web application, which connects to SharePoint (customer tenant) to create sites & various List.
To access the customers Sharepoint environment, I used OpenIdConnectAuthenticationOptions which prompts user for AAD creditinals and then the various options for which user wants to provide access ( This App and various API access needed is configured in AAD - when allowing access to "O365 SharePoint Online"
Using OpenIdConnectAutheticationOption, on AuthorizationCode Received the code is used to get "AccessToken".
Using this "AccessToken" to get clientContext gives error:
"401 - Not authorized"
How one can get the required token which allows CSOM operation?
The code used from -
Active Directory Dot net Webapp Multitenant
In the controller OnboardingController, Processcode function, after getting AcquireTokenByAuthorizationCodeAsync following code is used -
string siteUrl = "https://svtestsite.sharepoint.com/sites/powerapps";
ClientContext ctx = new ClientContext(siteUrl);
ctx.ExecutingWebRequest +=
delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
{
webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] =
"Bearer " + result.AccessToken; // accessToken;
};
ctx.Load(ctx.Web, p => p.Title);
ctx.ExecuteQuery();
Console.WriteLine(siteUrl);
Console.WriteLine(ctx.Web.Title);
The code snippet that you've shared, which sets bearer token for request Authorization header looks fine to me.
ctx.ExecutingWebRequest +=
delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
{
webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] =
"Bearer " + result.AccessToken; // accessToken;
};
So your issue could be with either the token itself or some permissions missing for app/users. Here are a few things that you should check:
Required permissions for your App Registered in Azure AD. Make sure the app has permissions for "Office 365 SharePoint Online". Which exact permissions depends on your requirements/use cases, but at least 1 should be there. Also, you should go through with the consent flow either using "Grant Permissions" button or as part of Onboarding flow for the tenant.
Check which "Resource" has the token been acquired for.
At least the sample code link you mention in your question (Active Directory Dot net Webapp Multitenant) mentions the resource as "https://graph.windows.net".
Make sure you have changed that to your SharePoint site collection URL.. something like "https://yoursite.sharepoint.com/".
Do try with including/excluding the / at the end of this URL. I've seen issues just because of that as well, although not sure if yours is related.
If above points don't work, it would be worthwhile to examine the access token you are sending to SharePoint in a tool like https://jwt.io or https://jwt.ms Especially, look for:
"aud" claim, which tells about the intended audience for which this token was issued. It should be your SharePoint URL. If it's not that can be causing the issue.
"tid" claim, to see that token is from correct Azure AD tenant. It will be GUID.
and other claims in token, to see if anything suspicious pops out.
Obvious one, but if you're using delegated permissions, then check that the user has appropriate permissions in SharePoint site collection.

How can I use an access token obtained from the Microsoft Graph API to access other APIs

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

.NET Gmail OAuth2 for multiple users

We are building a solution that will need to access our customers Gmail accounts to read/send mail. On account signup, we'd have a pop-up for our customer to do Gmail auth page and then a backend process to periodically read their emails.
The documentation doesn't seem to cover this use case. For example https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth says that client tokens should be stored in client_secrets.json - what if we have 1000s of clients, what then?
Service accounts are for non-user info, but rather application data. Also, if I use the GoogleWebAuthorizationBroker and the user has deleted access or the tokens have expired, I don't want my backend server app to pop open a web brower, as this seems to do.
I would imagine I could use IMAP/SMTP accomplish this, but I don't think it's a good idea to store those credentials in my db, nor do I think Google wants this either.
Is there a reference on how this can be accomplished?
I have this same situation. We are planning a feature where the user is approving access to send email on their behalf, but the actual sending of the messages is executed by a non-interactive process (scheduled task running on an application server).
I think the ultimate answer is a customized IAuthorizationCodeFlow that only supports access with an existing token, and will not execute the authorization process. I would probably have the flow simulate the response that occurs when a user clicks the Deny button on an interactive flow. That is, any need to get an authorization token will simply return a "denied" AuthorizationResult.
My project is still in the R&D phase, and I am not even doing a proof of concept yet. I am offering this answer in the hope that it helps somebody else develop a concrete solution.
While #hurcane's answer more than likely is correct (haven't tried it out), this is what I got working over the past few days. I really didn't want to have to de/serialize data from the file to get this working, so I kinda mashed up this solution
Web app to get customer approval
Using AuthorizationCodeMvcApp from Google.Apis.Auth.OAuth2.Mvc and documentation
Store resulting access & refresh tokens in DB
Use AE.Net.Mail to do initial IMAP access with access token
Backend also uses AE.Net.Mail to access
If token has expired, then use refresh token to get new access token.
I've not done the sending part, but I presume SMTP will work similarly.
The code is based on SO & blog posts:
t = EF object containing token info
ic = new ImapClient("imap.gmail.com", t.EmailAddress, t.AccessToken, AuthMethods.SaslOAuth, 993, true);
To get an updated Access token (needs error handling) (uses the same API as step #1 above)
using (var wb = new WebClient())
{
var data = new NameValueCollection();
data["refresh_token"] = refresh;
data["client_id"] = "(Web app OAuth id)";
data["client_secret"] = "(Web app OAuth secret)";
data["grant_type"] = "refresh_token";
var response = wb.UploadValues(#"https://accounts.google.com/o/oauth2/token", "POST", data);
string Tokens = System.Text.Encoding.UTF8.GetString(response);
var token = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(Tokens);
at = token.access_token;
return at;
}

Resources