Office 365 Authentication with Microsoft Graph - outlook

I previously used the following code for authentication on my application web server using a token obtained from Office.context.mailbox.getCallbackTokenAsync in my 365 client add-in:
ExchangeService service = new ExchangeService();
service.Credentials = new OAuthCredentials(token);
service.Url = new Uri(ewsUrl);
I am now trying to convert my EWS server code to Microsoft Graph, but I'm having trouble getting access to those APIs using that 365 token. I've tried something like this but to no avail:
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", token);
return System.Threading.Tasks.Task.CompletedTask;
}));
My Permissions in Azure should be okay as I've already been able to obtain various items and save into the system. Thanks for any help!

Graph and EWS have different audiences in their tokens, you cannot mix the two.

Related

Accessing User Credential Value from Azure.Identity

I have connected my Visual studio to Azure Active Directory by setting and manually entering pass/email.
This credential shows when I go to Azure Serice Authenticator in Visual Studio under tools.
I want to access the ID associated.
I came across this doc , and I want the AZURE_USERNAME , which must be associated with the id logged in.
I am unable to get a way by which I can access this value.
Does it mean that I can only access it if I explicitly set such value ?
--
Alternatively , is there any way by which I can access the Email ID of user signed in to Visual studio / or signed into Windows and not just User name
Yes of course we should preset environment variables.
Then we can use them for EnvironmentCredential authentication. The reverse will not work.
You can call Microsoft Graph API to get the user information.
Sample reference here.
You can implement the graphServiceClient like this:
var credential = new DefaultAzureCredential();
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }));
var accessToken = token.Token;
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.CompletedTask;
}));
A blog for your reference.

Application Permission support for Dynamics Customer Engagement Web API

We are planning to move from Organization Service to Common Data Service Web API so we could utilize OAuth 2.0 authentication instead of a service account which customer has some security concerns.
Once we did some prototype, we discovered that the Web API authentication is a little different from typical Graph API authentication. It only supports Delegated Permission. Thus a user credential must be presented for acquiring the access token.
Here is the Azure AD Graph API permission for CRM Web API:
Here is the code in acquiring the access token for the sample code at Web API Global Discovery Service Sample (C#)
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com", false);
UserCredential cred = new UserCredential(username, password);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, clientId, cred);
Here is another similar post Connect to Dynamics 365 Customer Engagement web services using OAuth although it is more than one year old.
Do you know when MS would support Application permission to completely eliminate the user from authentication? Or there is any particular reason to keep the user here. Thanks for any insights.
[Update 1]
With below answer from James, I did the modification for the code, here is my code
string clientId = "3f4b24d8-61b4-47df-8efc-1232a72c8817";
string secret = "xxxxx";
ClientCredential cred = new ClientCredential(clientId, secret);
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, cred);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri(GlobalDiscoUrl);
HttpResponseMessage response = client.GetAsync("api/discovery/v1.0/Instances", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
string result = response.Content.ReadAsStringAsync().Result;
JObject body = JObject.Parse(result);
JArray values = (JArray)body.GetValue("value");
if (!values.HasValues)
{
return new List<Instance>();
}
return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
so I am able to acquire the access token, but it still could not access the global discovery services.
Here is what the access token looks like:
{
"aud": "https://globaldisco.crm.dynamics.com/",
"iss": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"iat": 1565802457,
"nbf": 1565802457,
"exp": 1565806357,
"aio": "42FgYEj59uDNtwvxTLnprU0NYt49AA==",
"appid": "3f4b24d8-61b4-47df-8efc-1232a72c8817",
"appidacr": "1",
"idp": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"tid": "f8cdef31-a31e-4b4a-93e4-5f571e91255a",
"uti": "w8uwKBSPM0y7tdsfXtAgAA",
"ver": "1.0"
}
By the way, we did already create the application user inside CRM by following the instruction.
Anything I am missing here?
[Update 2]
For WhoAmI request, there are different results. If I am using latest MSAL and with authority "https://login.microsoftonline.com/AzureADDirectoryID/oauth2/authorize", I would be able to get the correct result. If I am using MSAL with "https://login.microsoftonline.com/common/oauth2/authorize", it won't work, I would get unauthorized error. If I am using ADAL 2.29, it is not working for both authority. Here is the working code:
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create("3f4b24d8-61b4-47df-8efc-1232a72cxxxx")
.WithClientSecret("xxxxxx")
// .WithAuthority("https://login.microsoftonline.com/common/oauth2/authorize", false)
.WithAuthority("https://login.microsoftonline.com/3a984a19-7f55-4ea3-a422-2d8771067f87/oauth2/authorize", false)
.Build();
var authResult = app.AcquireTokenForClient(new String[] { "https://crmxxxxx.crm5.dynamics.com/.default" }).ExecuteAsync().Result;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri("https://crm525842.api.crm5.dynamics.com/");
HttpResponseMessage response = client.GetAsync("api/data/v9.1/WhoAmI()", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content.
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
}
else
{
throw new Exception(response.ReasonPhrase);
}
The documentation isn't the easiest to follow, but from what I understand you should start with Use OAuth with Common Data Service.
You then have two subtle options when registering your app. The second does not require the Access Dynamics 365/Common Data Service as organization users permission
Giving access to Common Data Service
If your app will be a client which allows the authenticated user to
perform operations, you must configure the application to have the
Access Dynamics 365 as organization users delegated permission.
Or
If your app will use Server-to-Server (S2S) authentication, this step
is not required. That configuration requires a specific system user
and the operations will be performed by that user account rather than
any user that must be authenticated.
This is elaborated further.
Connect as an app
Some apps you will create are not intended to be run interactively by
a user. ... In these cases you can create a special application user
which is bound to an Azure Active Directory registered application and
use either a key secret configured for the app or upload a X.509
certificate. Another benefit of this approach is that it doesn't
consume a paid license.
Register your app
When registering an app you follow many of the same steps ... with the
following exceptions:
You do not need to grant the Access Dynamics 365 as organization users permission.
You will still have a system user record in Dynamics to represent the application registration. This supports a range of basic Dynamics behaviours and allows you to apply Dynamics security to you app.
As opposed to a username and password you can then use the secret to connect.
string serviceUrl = "https://yourorg.crm.dynamics.com";
string clientId = "<your app id>";
string secret = "<your app secret>";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
ClientCredential credential = new ClientCredential(clientId, secret);
AuthenticationResult result = authContext.AcquireToken(serviceUrl, credential);
string accessToken = result.AccessToken;
Or a certificate.
string CertThumbPrintId = "DC6C689022C905EA5F812B51F1574ED10F256FF6";
string AppID = "545ce4df-95a6-4115-ac2f-e8e5546e79af";
string InstanceUri = "https://yourorg.crm.dynamics.com";
string ConnectionStr = $#"AuthType=Certificate;
SkipDiscovery=true;url={InstanceUri};
thumbprint={CertThumbPrintId};
ClientId={AppID};
RequireNewInstance=true";
using (CrmServiceClient svc = new CrmServiceClient(ConnectionStr))
{
if (svc.IsReady)
{
...
}
}
You may also want to check out Build web applications using Server-to-Server (S2S) authentication which appears to be a similar (but different).
Use server-to-server (S2S) authentication to securely and seamlessly
communicate with Common Data Service with your web applications and
services. S2S authentication is the common way that apps registered on
Microsoft AppSource use to access the Common Data Service data of
their subscribers. ... Rather than user credentials, the application is authenticated based on a service principal identified by an Azure AD Object ID value which is stored in the application user record.
Aside; if you are currently using the Organization Service .NET object, that is being migrated to using the Web API internally.
Microsoft Dynamics CRM 2011 endpoint
The Dynamics 365 SDK assemblies will be updated to use the Web API.
This update will be fully transparent to you and any code written
using the SDK itself will be supported.

Azure AD userAssertion: Token missing scope "Directory.Read.All"

I have a Web API and a UI application that used ADAL library to call the Web API.
I already gave DELEGATED PERMISSIONS (Read directory data) for both Web API and UI application while registering the apps to Azure AD.
I have below code in Web API to save Token for log-In user,
private void ConfigureAuthentication(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true, ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] }
});
}
Now in Web API controllers, I am trying to get token to access Microsoft AD Graph API using below code,
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = "test#onmicrosoft.com";
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, aadInstance, tenant));
var clientCred = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync("https://graph.windows.net", clientCred, userAssertion);
accessToken = result.AccessToken;
The above code giving me token back but the scope values is below,
`scp: "User.Read"`
Question - Why the token not giving directory access (Directory.Read.All) as I already set Directory access?
`scp: "Directory.Read.All User.Read"`
Update:
I am missing Grant Permission for Read Directory Data under DELEGATED PERMISSIONS. After giving Grant Permission I am able to get token with scope scp: "Directory.Read.All User.Read"
If I understand correctly, you want to use Microsoft Graph API ,not Azure AD Graph API.
However, based on that the screenshot you post in this question is a v1 enpoint AAD Application, it does nothing about Microsoft Graph API which you're trying to approaching. So, whatever you changed on this application, the result shouldn be same. I suggest you register v2 enpoint Application in https://apps.dev.microsoft.com/
Here is a document which shows how to get auth tokens for using Microsoft Graph.
Hope this helps!

Migration of Google Apps Marketplace app to oAuth 2.0 with additional scopes

We have app using oauth 1.0 in old marketplace. We are in process of migrating to oauth 2.0 for new marketplace. We are using UpgradeableApp API to do migration for existing domains. I am following steps specified here : https://developers.google.com/apps-marketplace/v1migratev2
As mentioned in the prerequisites in the above link: The scopes for the new and old apps must be compatible. But our new app has some additional scopes. Is there any way to grant access to these additional scopes while doing migration.
Only domain's admin or users can approve additional scopes.
Domain's admin receives an email notification after upgrade.
In your oauth2.0 app you can detect if all scopes have been approved or not. If not, you can show the user appropriate message to contact domain admin to get scopes approved.
For this we should have same scope in both old as well as on new listing. I am also facing the same problem of migrating the old users to new one. Kindly check the below code how I am migrating from old to new Users but every time I am getting 401 UnAuthorized, May I know what I am missing for this.
String url = String.Format("https://www.googleapis.com/appsmarket/v2/upgradableApp/{0}/{1}/{2}", oldAppId, chromeListing, domain);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "PUT";
request.ContentType = "application/json";
request.Accept = "application/json";
request.ProtocolVersion = HttpVersion.Version11;
request.Credentials = CredentialCache.DefaultCredentials;
request.Headers.Add("Authorization", "OAuth");
Hashtable postObj = new Hashtable();
postObj["Consumer Key"] = oldClientId;
postObj["Consumer Key Secret"] = oldSecret;
String s1 = new JavaScriptSerializer().Serialize(postObj);
var bs = Encoding.UTF8.GetBytes(s1);
using (Stream reqStream = request.GetRequestStream())
{
reqStream.Write(bs, 0, bs.Length);
}
using (WebResponse response = request.GetResponse())
{
using (var sr = new StreamReader(response.GetResponseStream()))
{
result = sr.ReadToEnd();
sr.Close();
}
}

Can I use the Dynamics CRM 4.0 SDK against a hosted IFD system?

I am running this code (with names and security details obscured). When I do, I get 401 unauthorised. The credentials are that of the user on the hosted server. Is this possible against an IFD system?
var token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "myorganisation";
CrmService service = new CrmService();
service.Url = "https://myorganisation.dynamicsgateway.com/mscrmservices/2007/crmservice.asmx";
service.CrmAuthenticationTokenValue = token;
service.Credentials = new NetworkCredential("bob.smith", "Password", "HOSTEDCRM");
var request = new RetrieveMultipleRequest();
request.Query = new QueryExpression
{
ColumnSet = new ColumnSet(new string[] { "name" }),
EntityName = "account"
};
var response = service.Execute(request);
I assume this code is outside of the CRM Website? In that case you'll want to add a reference to the discovery service as Mercure points out above. You'll want to execute a RetrieveCrmTicketRequest against the discovery service to get a ticket good for connecting to the Crm Services.
In your CRM Authentication Token you'll want to set the authentication type to 2 (IFD). Then set the CrmTicket property on the token to the ticket you got from your RetrieveCrmTicketResponse.
I also set the URL based on that response, but you could continue to hard code it.
You will want to continue to set the Credentials on the service.
I use a single user to connect to CRM and cache that ticket (an expiration date is in the response from the discovery service). That way I can bypass the discovery service on future requests. There is an error code to look for to go after the ticket again, but I don't have it off hand.
Yes, it's possible, you are only missing a little pieces, the CrmAuthenticationToken.ExtractCrmAuthenticationToken.
Check out this great explaination on Dynamics Forum http://social.microsoft.com/Forums/en-US/crmdevelopment/thread/81f8ba82-981d-40dd-893d-3add67436478

Resources