How do I connect a server service to Dynamics Online - c#

I am modifying an internal management application to connect to our online hosted Dynamics 2016 instance.
Following some online tutorials, I have been using an OrganizationServiceProxy out of Microsoft.Xrm.Sdk.Client from the SDK.
This seems to need a username and password to connect, which works fine, but I would like to connect in some way that doesn't require a particular user's account details. I don't think the OAuth examples I've seen are suitable, as there is no UI, and no actual person to show an OAuth request to.
public class DynamicsHelper
{
private OrganizationServiceProxy service;
public void Connect(string serviceUri, string username, string password)
{
var credentials = new ClientCredentials();
credentials.UserName.UserName = username;
credentials.UserName.Password = password;
var organizationUri = new Uri(serviceUri);
this.service = new OrganizationServiceProxy(organizationUri, null, credentials, null);
}
}
Is there a way to connect with an application token or API key?

I've found that to do this successfully, you'll need to setup all of the following:
Create an application registration in Azure AD:
grant it API permissions for Dynamics, specifically "Access Dynamics 365 as organization users"
give it a dummy web redirect URI such as http://localhost/auth
generate a client secret and save it for later
Create a user account in Azure AD and give it permissions to Dynamics.
Create an application user record in Dynamics with the same email as the non-interactive user account above.
Authenticate your application using the user account you've created.
For step 4, you'll want to open an new incognito window, construct a url using the following pattern and login using your user account credentials in step 2:
https://login.microsoftonline.com/<your aad tenant id>/oauth2/authorize?client_id=<client id>&response_type=code&redirect_uri=<redirect uri from step 1>&response_mode=query&resource=https://<organization name>.<region>.dynamics.com&state=<random value>
When this is done, you should see that your Dynamics application user has an Application ID and Application ID URI.
Now with your ClientId and ClientSecret, along with a few other organization specific variables, you can authenticate with Azure Active Directory (AAD) to acquire an oauth token and construct an OrganizationWebProxyClient. I've never found a complete code example of doing this, but I have developed the following for my own purposes. Note that the token you acquire has an expiry of 1 hr.
internal class ExampleClientProvider
{
// Relevant nuget packages:
// <package id="Microsoft.CrmSdk.CoreAssemblies" version="9.0.2.9" targetFramework="net472" />
// <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="4.5.1" targetFramework="net461" />
// Relevant imports:
// using Microsoft.IdentityModel.Clients.ActiveDirectory;
// using Microsoft.Crm.Sdk.Messages;
// using Microsoft.Xrm.Sdk;
// using Microsoft.Xrm.Sdk.Client;
// using Microsoft.Xrm.Sdk.WebServiceClient;
private const string TenantId = "<your aad tenant id>"; // from your app registration overview "Directory (tenant) ID"
private const string ClientId = "<your client id>"; // from your app registration overview "Application (client) ID"
private const string ClientSecret = "<your client secret>"; // secret generated in step 1
private const string LoginUrl = "https://login.microsoftonline.com"; // aad login url
private const string OrganizationName = "<your organization name>"; // check your dynamics login url, e.g. https://<organization>.<region>.dynamics.com
private const string OrganizationRegion = "<your organization region>"; // might be crm for north america, check your dynamics login url
private string GetServiceUrl()
{
return $"{GetResourceUrl()}/XRMServices/2011/Organization.svc/web";
}
private string GetResourceUrl()
{
return $"https://{OrganizationName}.api.{OrganizationRegion}.dynamics.com";
}
private string GetAuthorityUrl()
{
return $"{LoginUrl}/{TenantId}";
}
public async Task<OrganizationWebProxyClient> CreateClient()
{
var context = new AuthenticationContext(GetAuthorityUrl(), false);
var token = await context.AcquireTokenAsync(GetResourceUrl(), new ClientCredential(ClientId, ClientSecret));
return new OrganizationWebProxyClient(new Uri(GetServiceUrl()), true)
{
HeaderToken = token.AccessToken,
SdkClientVersion = "9.1"
};
}
public async Task<OrganizationServiceContext> CreateContext()
{
var client = await CreateClient();
return new OrganizationServiceContext(client);
}
public async Task TestApiCall()
{
var context = await CreateContext();
// send a test request to verify authentication is working
var response = (WhoAmIResponse) context.Execute(new WhoAmIRequest());
}
}

With Microsoft Dynamics CRM Online or internet facing deployments
When you use the Web API for CRM Online or an on-premises Internet-facing deployment (IFD)
you must use OAuth as described in Connect to Microsoft Dynamics CRM web services using OAuth.
Before you can use OAuth authentication to connect with the CRM web services,
your application must first be registered with Microsoft Azure Active Directory.
Azure Active Directory is used to verify that your application is permitted access to the business data stored in a CRM tenant.
// TODO Substitute your correct CRM root service address,
string resource = "https://mydomain.crm.dynamics.com";
// TODO Substitute your app registration values that can be obtained after you
// register the app in Active Directory on the Microsoft Azure portal.
string clientId = "e5cf0024-a66a-4f16-85ce-99ba97a24bb2";
string redirectUrl = "http://localhost/SdkSample";
// Authenticate the registered application with Azure Active Directory.
AuthenticationContext authContext =
new AuthenticationContext("https://login.windows.net/common", false);
AuthenticationResult result =
authContext.AcquireToken(resource, clientId, new Uri(redirectUrl));
P.S: Concerning your method, it is a best practice to not to store the password as clear text, crypt it, or encrypt the configuration sections for maximum security.
See walkhrough here
Hope this helps :)

If I understand your question correctly, you want to connect to Dynamics 2016 (Dynamics 365) through a Registerd Azure Application with ClientId and Secret, instead of Username and Password. If this is correct, yes this is possible with the OrganizationWebProxyClient . You can even use strongly types assemblies.
var organizationWebProxyClient = new OrganizationWebProxyClient(GetServiceUrl(), true);
organizationWebProxyClient.HeaderToken = authToken.AccessToken;
OrganizationRequest request = new OrganizationRequest()
{
RequestName = "WhoAmI"
};
WhoAmIResponse response = organizationWebProxyClient.Execute(new WhoAmIRequest()) as WhoAmIResponse;
Console.WriteLine(response.UserId);
Contact contact = new Contact();
contact.EMailAddress1 = "jennie.whiten#mycompany.com";
contact.FirstName = "Jennie";
contact.LastName = "White";
contact.Id = Guid.NewGuid();
organizationWebProxyClient.Create(contact);
To get the AccessToken, please refer to the following post Connect to Dynamics CRM WebApi from Console Application.
Replace line 66 (full source code)
authToken = await authContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUrl), new PlatformParameters(PromptBehavior.Never));
with
authToken = await authContext.AcquireTokenAsync( resourceUrl, new ClientCredential(clientId, secret));
You can also check the following Link Authenticate Azure Function App to connect to Dynamics 365 CRM online that describes how to secure your credentials using the Azure Key Vault.

Related

Creating an Azure DevOPS Personal Access Token (PAT) using C#

I am trying to create a PAT using the new capabilities in the TokensHttpClient. However I keep getting authorisation exception. I am using my Microsoft account which is an organization administrator.
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
// Connect to Azure DevOps Services
VssConnection connection = new VssConnection(_uri, creds);
connection.ConnectAsync().SyncResult();
var t = connection.GetClient<TokenAdminHttpClient>();
//next line works as expected
var tokens = t.ListPersonalAccessTokensAsync(connection.AuthorizedIdentity.SubjectDescriptor).Result;
var tokenAdmin = connection.GetClient<TokensHttpClient>();
PatTokenCreateRequest createRequest = new PatTokenCreateRequest();
createRequest.DisplayName = "Niks_Api_Token";
createRequest.Scope = "vso.work_full";
createRequest.ValidTo = DateTime.Now.AddYears(1);
//this is where authorization exception occurs
var result = tokenAdmin.CreatePatAsync(createRequest).Result;
To manage personal access tokens with APIs, you must authenticate with an Azure AD token. Azure AD tokens are a safer authentication mechanism than using PATs. Given this API’s ability to create and revoke PATs, we want to ensure that such powerful functionality is given to allowed users only.
Please check the Prerequisites here.
Make sure your org has been connect to AAD, see here.
Please register an application in Azure AD, make sure the client secret has been created. You can refer to this doc. And add the permission of Azure DevOps.
The sample code to get Azure AD access token.
public static async Task<string> GetAccessTokenAsyncByClientCredential()
{
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder.Create(<appId/clientId>)
.WithTenantId(<tenantId>)
.WithClientSecret(<clientSecret>)
.Build();
string[] scopes = new string[] { "499b84ac-1321-427f-aa17-267ca6975798/.default" };
var result = await cca.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
}

Authentication with p12 key give error User cannot access account

I have a MVC web-application that update the products on the eCommerce site. Now we enrolled into the google merchant center and my objective is to update the products at the same time. I am using the Google.Apis.ShoppingContent.v2_1 API.
This is my API Credentials
This is my API service account
I have used the google account email address for the user as well as the service account email but with the same result.
I have the following
static string[] Scopes = { ShoppingContentService.Scope.Content};
static string P12Secret = #"~\Content\XXXXXX-5cab03fb904a.p12";
static string userName = "serviceaccount#gserviceaccount.com";
static public async Task RunTest2()
{
var certificate = new X509Certificate2(P12Secret, "notasecret", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(userName)
{
Scopes = Scopes
}.FromCertificate(certificate));
var service = new ShoppingContentService(new BaseClientService.Initializer
{
ApplicationName = ApplicationName,
HttpClientInitializer = credential
});
try
{
var result = await service.Products.List("My MerchantID").ExecuteAsync();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
When I execute var result = await service.Products.List("My MerchantID").ExecuteAsync(); I get the error
e.Message = "Google.Apis.Requests.RequestError\nUser cannot access account 123456789 [401]\r\nErrors [\r\n\tMessage[User cannot access account 123456789 ] Location[ - ] Reason[auth/account_access_denied] Domain[content.ContentErrorDomain]\r\n]\r\n"
Documentaiton
Service accounts are special Google accounts that can be used by applications to access Google APIs programmatically via OAuth 2.0. A service account uses an OAuth 2.0 flow that does not require human authorization. Instead, it uses a key file that only your application can access. This guide discusses how to access the Content API for Shopping with service accounts.
Service accounts need to be pre authorized. If its not then it doesnt have access to any data.
User cannot access account 123456789
Means that it does not have access you have forgotten to grant it access. Check the Documentaiton look for the section below follow all of the steps.

azure deploy multiple site's instance on demand

I have a website called www.Request.com, when users access this site they will be able to request the creation of a new instance of another website that is already deployed in AZURE with the name www.MyTechnicalApp.com
for example when I access to www.Request.com I will request the creation of MyTechnicalApp for my company called "MyCompany", it's supposed that there is a script that will be executed by request.com website to create automatically www.MyCompany.MyTechnicalApp.com website.
would you please let me know how could I do that?
According to your description, to create a web app on Azure automatically, there are two ways to achieve this.
One: using "Windows Azure Management Libraries", this SDK is a wrapper around "Azure Service Management" API.
First, we need to do authentication with ASM API and we can refer to: Windows Azure Management Librairies : Introduction et authentification, then we will be able to create a website with something like this:
using (var AwsManagement = new Microsoft.WindowsAzure.Management.WebSites.WebSiteManagementClient(azureCredentials))
{
WebSiteCreateParameters parameters = new WebSiteCreateParameters()
{
Name = "myAws",
// this Service Plan must be created before
ServerFarm = "myServiceplan",
};
await AwsManagement.WebSites.CreateAsync("myWebSpace", parameters, CancellationToken.None);
}
Two: We can create a web site by using a POST request that includes the name of the web site and other information in the request body. We can check the code example for azure-sdk-for-net
use this link to get the credentials Authentication in Azure Management Libraries for Java.
https://github.com/Azure/azure-libraries-for-java/blob/master/AUTH.md
The below link helped me to find the answer.
static void Main(string[] args)
{
try
{
var resourceGroupName = "your ressource group name";
var subId = "64da6c..-.......................88d";
var appId = "eafeb071-3a70-40f6-9e7c-fb96a6c4eabc";
var appSecret = "YNlNU...........................=";
var tenantId = "c5935337-......................19";
var environment = AzureEnvironment.AzureGlobalCloud;
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(appId, appSecret, tenantId, AzureEnvironment.AzureGlobalCloud);
var azure = Microsoft.Azure.Management.Fluent.Azure
.Configure()
.Authenticate(credentials)
.WithSubscription(subId);
azure.AppServices.WebApps.Inner.CreateOrUpdateHostNameBindingWithHttpMessagesAsync(resourceGroupName, "WebSiteName", "SubDomainName",
new HostNameBindingInner(
azureResourceType: AzureResourceType.Website,
hostNameType: HostNameType.Verified,
customHostNameDnsRecordType: CustomHostNameDnsRecordType.CName)).Wait();
}
catch (Exception ex)
{
}
}

Operation returned an invalid status code 'Unauthorized''

I am trying to embed a sample Power BI dashboard in a WPF application following the sample project and sort of tutorial from here. When I launch the app, I have to enter my password to authenticate myself and when it tries to get the list of my Power BI workspaces with the getAppWorkspacesList() I am getting this error message
Microsoft.Rest.HttpOperationException: 'Operation returned an invalid
status code 'Unauthorized''
Can someone please help in pointing out why this error is occuring? I tried to look into the details of the error, but I am not understanding what could be causing the issue. I was able to embed a dashboard in a .Net Web App without an issue, so I don't think the problem is in my Power BI or Azure Directory account.
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
Uri redirectUri = new Uri(ConfigurationManager.AppSettings["ida:RedirectUri"]);
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static string graphResourceId = ConfigurationManager.AppSettings["ida:ResourceId"];
private AuthenticationContext authContext = null;
TokenCredentials tokenCredentials = null;
string Token = null;
string ApiUrl = "https://api.powerbi.com";
public MainWindow()
{
InitializeComponent();
TokenCache TC = new TokenCache();
authContext = new AuthenticationContext(authority, TC);
}
private void getAppWorkspacesList()
{
using (var client = new PowerBIClient(new Uri(ApiUrl), tokenCredentials))
{
appWorkSpacesList.ItemsSource = client.Groups.GetGroups().Value.Select(g => new workSpaceList(g.Name, g.Id));
}
}
Based on your description, I assumed that you are using the Access token for Power BI users (user owns data) approach. I would recommend you use https://jwt.io/ to decode the access_token after successfully invoked authContext.AcquireTokenAsync. Make sure the aud is https://analysis.windows.net/powerbi/api and check the permissions scope property scp.
For Get Groups, the required scope would look as follows:
Required scope: Group.Read.All or Group.ReadWrite.All or Workspace.Read.All or Workspace.ReadWrite.All
You could also use fiddler or postman to simulate the request against the get groups endpoint with the access_token received in your WPF application to narrow down this issue.
Moreover, you could follow Register an application to check your Azure AD app and make sure the required delegated permissions to Power BI Service (Microsoft.Azure.AnalysisServices) API have been correctly configured.
We got the same error when we use app owns data approach. The way to solve that is described here.
Basically, the way to get access token documented in Microsoft website does not work. We end up making a REST API call to https://login.microsoftonline.com/common/oauth2/token and post the following data:
grant_type: password
scope: openid
resource: https://analysis.windows.net/powerbi/api
client_id: APPLICATION_ID
client_secret: APPLICATION_SECRET
username: USER_ID
password: USER_PASSWORD
You will get a JSON back and then you can get the access_token which will be used when creating power bi client like this:
var mTokenCredentials = new TokenCredentials(accessToken, "Bearer");
using (var client = new PowerBIClient(new Uri("https://api.powerbi.com"), mTokenCredentials))
I hope this can help someone. This is the original post.

B2B access in multi tenant application

I have succesfully setup a multi tenant application.
For now, I am able to authenticate the user and use tokens to access other resources. (Microsoft Graph & Microsoft AD Graph)
Now I want to get B2B working.
Current flow:
- User signs in
- AuthorizationCodeReceived gets the acquires the token (via $commonAuthority endpoint)
- When requesting a token for the Ad Graph, I am using the $tenantAuthority
This works perfectly when $tenantAuthority is the same tenant authority as the one where the account was created in.
However, if I login with another user (from another tenant, given trust to the actual tenant) and use $tenantAuthority = trusted authority, then I always the following error:
Failed the refresh token:
AADSTS65001: The user or administrator has not consented to use the application with ID
If I change $tenantAuthority to the 'source' tenant authority where the user was created in, everything works fine.
Any help would be greatly appreciated.
Update: Code sample
App has two tenants (tenantA en tenantB) and I will use a user from tenantB with tenantA given a trust to this user.
AuthorizationCodeReceived = async context =>
{
TenantContext.TenantId = "someguid";
var tenantId =
TenantContext.TenantId;
// get token cache via func, because the userid is only known at runtime
var getTokenCache = container.Resolve<Func<string, TokenCache>>();
var userId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var tokenCache = getTokenCache(userId);
var authenticationContext = new AuthenticationContext($"{configuration.Authority}",
tokenCache);
await authenticationContext.AcquireTokenByAuthorizationCodeAsync(
context.Code,
new Uri(context.Request.Uri.GetLeftPart(UriPartial.Authority)),
new ClientCredential(configuration.ClientId, configuration.ClientSecret),
configuration.GraphResourceId);
}
This code works perfectly. Login in with a user from both tenants works perfectly.
But when I need the Graph Service Client or ActiveDirectoryClient, I need to obtain access tokens to been able to address an api for a certain tenant. I retrieve the access tokens like this:
public IGraphServiceClient CreateGraphServiceClient()
{
var client = new GraphServiceClient(
new DelegateAuthenticationProvider(
async requestMessage =>
{
Logger.Debug("Retrieving authentication token to use in Microsoft Graph.");
string token;
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var authenticationContext =
new AuthenticationContext($"{_configuration.TenantAuthorityPrefix}{currentUserHomeTenantId}",
_tokenCacheFactoryMethod(currentUserObjectId));
var clientCredential = new ClientCredential(_configuration.ClientId, _configuration.ClientSecret);
try
{
token = await GetTokenSilently(authenticationContext, _configuration.GraphResourceId, currentUserObjectId);
}
catch (AdalSilentTokenAcquisitionException e)
{
Logger.Error("Failed to retrieve authentication token silently, trying to refresh the token.", e);
var result = await authenticationContext.AcquireTokenAsync(_configuration.GraphResourceId, clientCredential);
token = result.AccessToken;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderKeys.Bearer, token);
}));
return client;
}
public IActiveDirectoryClient CreateAdClient()
{
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var graphServiceUrl = $"{_configuration.AdGraphResourceId}/{currentUserHomeTenantId}";
var tokenCache = _tokenCacheFactoryMethod(currentUserObjectId);
var client = new ActiveDirectoryClient(new Uri(graphServiceUrl),
() => GetTokenSilently(
new AuthenticationContext(
$"{_configuration.TenantAuthorityPrefix}{ClaimsPrincipal.Current.FindFirst(ClaimTypes.TenantId).Value}", tokenCache
),
_configuration.AdGraphResourceId, currentUserObjectId
));
return client;
}
When I do a request with one of the two client SDK's, I got the following error:
Failed the refresh token: AADSTS65001: The user or administrator has not consented to use the application with ID.
Changing the catch method when retrieving the Token did the trick:
if(e.ErrorCode == "failed_to_acquire_token_silently")
{
HttpContext.Current.Response.Redirect(authenticationContext.GetAuthorizationRequestUrlAsync(resourceId, _configuration.ClientId, new Uri(currentUrl),
new UserIdentifier(currentUserId, UserIdentifierType.UniqueId), string.Empty);
}
I don't see that you mention that so: in a B2B collaboration you've to invite user from other tenant first. The steps are like that:
invite and authorize a set of external users by uploading a comma-separated values - CSV file
Invitation will be send to external users.
The invited user will either sign in to an existing work account with Microsoft (managed in Azure AD), or get a new work account in Azure AD.
After signed in, user will be redirected to the app that was shared with them
That works perfectly in my case.
Regarding some problems which I've detect:
Trailing "/" at the end of the active directory resource - try to remove it as this may cause problems. Bellow you will find some code to get authentication headers:
string aadTenant = WebServiceClientConfiguration.Settings.ActiveDirectoryTenant;
string clientAppId = WebServiceClientConfiguration.Settings.ClientAppId;
string clientKey = WebServiceClientConfiguration.Settings.ClientKey;
string aadResource = WebServiceClientConfiguration.Settings.ActiveDirectoryResource;
AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant);
ClientCredential clientCredential = new ClientCredential(clientAppId, clientKey);
UserPasswordCredential upc = new UserPasswordCredential(WebServiceClientConfiguration.Settings.UserName, WebServiceClientConfiguration.Settings.Password);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(aadResource, clientAppId, upc);
return authenticationResult.CreateAuthorizationHeader();
Applications provisioned in Azure AD are not enabled to use the OAuth2 implicit grant by default. You need to explicitly opt in - more details can be found here: Azure AD OAuth2 implicit grant

Categories

Resources