I'm having trouble getting OAuth credentials to work with EWS in Office 365. At a high level I'm writing a SharePoint 2013 app and I'm trying to access the user's mailbox data in Exchange. I did verify my EWS code is 'correct' by swapping out the OAuth code for a hard coded username and password and it worked perfectly.
I get a token back using the code below, however I'm getting a 401 when I try to get the access the user's inbox. I left it off for brevity, but I am passing the token into a new OAuthCredentials object before accessing the inbox.
string acsUrl = "https://accounts.accesscontrol.windows.net/";
using (WebClient exchangeTokenClient = new WebClient())
{
exchangeTokenClient.BaseAddress = acsUrl;
NameValueCollection requestParams = new NameValueCollection();
requestParams.Add("grant_type", "client_credentials");
requestParams.Add("client_id", "<clientid>#<realm>");
requestParams.Add("client_secret", "<client secret>");
requestParams.Add("resource", "00000002-0000-0ff1-ce00-000000000000/outlook.office365.com#<realm>");
exchangeTokenClient.Headers.Add("Authorization", "Bearer " + ((SharePointAcsContext)spContext).UserAccessTokenForSPAppWeb);
byte[] responseBytes = exchangeTokenClient.UploadValues("<realm>/tokens/OAuth/2", "POST", requestParams);
string response = Encoding.UTF8.GetString(responseBytes);
}
The more that I think about this, the more I wonder if my 'app' needs rights on the exchange server and if that is what the root cause of the 401 is.
Has anyone actually done this? I feel like it should be possible, but I can't seem to find a lot of documentation on the process.
Thanks
Joe
Part #1:
Hi Joe, you can't quite do it this way. It seems you try to re-play a SharePoint token to the Exchange endpoint. Exchange will reject the SharePoint token, as the token is not for the Exchange endpoint (it fails the audience check).
Now to get it to work if you want to do Auth the only way for Exchange is to use the new OAuth2 model we announced at SPC. You can get more details from my session presentation at "http://www.sharepointconference.com/content/sessions/SPC379" (I believe we post the slides in a few days). There are three documents out there that I can also recommend to take a look, that is: Authentication and authorization using Common Consent Framework: "http://msdn.microsoft.com/en-us/library/dn605895(v=office.15).aspx", ...
Part #2
... How to: Integrate O365 with a web server app using Common Consent Framework at "http://msdn.microsoft.com/en-us/library/dn605894(v=office.15).aspx" and "Using the Mail, Calendar, and Contact REST APIs to work with emails, calendar items, and contacts" at "http://msdn.microsoft.com/en-us/library/dn605896(v=office.15).aspx"
Hope this helps,
Cheers!
Matthias
Related
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.
Im working a Laravel Project where we need to get the form details and send them to users to get sign through docusign. I refereed the PHP SDK but not sure where to get the JWT TOKEN, since the normal authentication is asking for docusign credentials each time when submit, we planned to use JWT authentication, please correct me if their are any options apart for this. Reviewed the Rest API, they mentioning the JWT will expire in 1hr, so should we manually generate JWT every 1hr. Please let me know wat is the best approach for handling this.
with JWT, you only need consent once in a lifetime and then you can generate tokens whenever you need.
You can get started by cloning this repo and following instructions to see how DocuSign APIs work with JWT.
The relevant code for getting a JWT token using PHP is this:
public function login() {
self::$access_token = $this->configureJwtAuthorizationFlowByKey();
self::$expiresInTimestamp = time() + self::$expires_in;
if(is_null(self::$account)) {
self::$account = self::$apiClient->getUserInfo(self::$access_token->getAccessToken());
}
$redirectUrl = false;
if (isset($_SESSION['eg'])) {
$redirectUrl = $_SESSION['eg'];
}
self::authCallback($redirectUrl);
}
To gain consent, open a browser (only once needed) with this URL - https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=<YOUR_INT_KEY>&redirect_uri=<YOUR_REDIRECT_URI>
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'
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;
}
My solution may not be technically possible. Yet I would like your viewpoint
We have to create a workflow/plugin MS-CRM 2013 which will publish events from CRM to a particular facebook page.
A separate application is not possible.
The problem is, to publish I need Page Access Token.
To get Page Access Token I need User Access Token.
To get User Access Token I need to follow the redirect_uri path where I will get the code in URL string but MS-CRM 2013 as you might be aware does not allow "code" as url string, it will refuse to redirect itself to my callback page, that is my problem.
I am open to suggestions I have used Facebook SDK and simple webrequest.this gives me the App Access Token
dynamic result = obj.Get("oauth/access_token", new
{
client_id = this.fbAppID,
client_secret = this.fbAppSecret,
grant_type = "client_credentials",
scope = "publish_actions,manage_pages,create_event,read_stream,publish_stream,email,read_friendlists,read_insights,read_requests,manage_friendlists,user_about_me,user_activities,user_birthday,user_groups,friends_groups"
});
Now How do I get the user access token from inside MS-CRM 2013 online.
If somebody has done it please do let me know, if you need more clarification I will be happy to provide more code etc.
You need to separate your logic:
Create an HTML/JavaScript WebResource combo that allows a user to link their CRM SystemUser record to Facebook. Build code similar to that below - it'll need additional supporting code to check if the user is already connected to Facebook, etc.
FB.login(function (response) {
if (response.authResponse) {
// user sucessfully logged in
var accessToken = response.authResponse.accessToken;
//TODO: Add logic to save accessToken to CRM SystemUser record.
}
}, { scope: 'email,publish_stream,email,rsvp_event,read_stream,user_likes,user_birthday' });
In your plugin retrieve the FB Access Token, saved in step 1, from the CRM SystemUser record, and use that to instantiate your Facebook connection object:
var obj = new FacebookClient(accessToken);
This is a bunch of work to do to get the access token. And none of the guides are going to clearly explain mixing pure HTML/JS for token retrieval but making the calls from C#, since this is a fairly unusual requirement.