I am in the situation that I need to access a ASP.NET Web Api that is using ADFS for authentication. I can hit it reliably through my browser by going through the ADFS login portal and getting the relevant FedAuth cookie. Unfortunately I need to access it from outside of a dedicated browser for use in a mobile app. The project is pretty much a slightly modified version of the standard visual studio web api template set up for Work and School Authentication (on-premises) and set up for cookie authentication.
bit of code from Startup.Auth.cs:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
}
I can't seem to figure out where to start. I've tried requesting a access token from the ADFS and can get different versions of SAML assertions using relevant login info, but it gets rejected by the web API. Have I misunderstood how it's supposed to work?
From my understanding it's supposed to go like this:
How I think it's supposed to work
App requests a authentication token from the ADFS
ADFS gives the requestee an auth token if the information provided was correct
App makes request to the web API and sending the token along inside a cookie called FedAuth(by default anyway) as a base64 encoded string
Web Api sends the token to the ADFS to find out if the token is correct.
ADFS responds with some sort of success message
Web Api responds to the app either with a rejection or a piece of data depending on how authentication went.
This is what I have right now while trying to figure out how to get a hold of the correct tokens.
using System;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Tokens;
using System.Net;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Security;
using Thinktecture.IdentityModel.Extensions;
using Thinktecture.IdentityModel.WSTrust;
namespace ConsoleApplication1
{
class Program
{
private const string UserName = "USERNAME";
private const string Password = "PASSWORD";
private const string Domain = "DOMAIN";
private const string ADFSEndpoint = "ADFS ENDPOINT";
private const string ApiBaseUri = "THE API";
private const string ApiEndPoint = "AN ENDPOINT";
static void Main(string[] args)
{
SecurityToken token = RequestSecurityToken(); // Obtain security token from ADFS.
CallApi(token); // Call api.
Console.ReadKey(); // Stop console from closing
}
private static SecurityToken RequestSecurityToken()
{
var trustChannelFactory =
new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(new Uri(ADFSEndpoint)))
{
TrustVersion = TrustVersion.WSTrust13,
Credentials = { UserName = { UserName = UserName + "#" + Domain, Password = Password } },
};
var requestSecurityToken = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
AppliesTo = new EndpointReference(ApiBaseUri)
};
RequestSecurityTokenResponse response;
var securityToken = trustChannelFactory.CreateChannel().Issue(requestSecurityToken, out response);
return securityToken;
}
private static async void CallApi(SecurityToken securityToken)
{
using (var handler = new HttpClientHandler { CookieContainer = new CookieContainer() })
{
using (var client = new HttpClient(handler))
{
handler.CookieContainer.MaxCookieSize = 8000; // Trying to make sure I can fit it in the cookie
var cookie = new Cookie {
Name = "FedAuth",
Value = Base64Encode(securityToken.ToTokenXmlString()),
HttpOnly = true,
Secure = true
};
handler.CookieContainer.Add(new Uri(ApiBaseUri), cookie);
var response = client.GetAsync(new Uri(ApiBaseUri + ApiEndPoint)).Result;
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
}
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
}
}
I can't quite remember what code I based my example of, but if anyone can point me in the right direction or tell me where I fucked up I'd appreciate it.
Edit: Sorry, forgot to add what I am getting.
The Web Api vomits out a bunch of debug information because an exception was thrown, telling me that a SecurityContextToken is expected instead of a saml:Assertion that I am apparently getting. Maybe my googlefoo is not powerful enough, but I can't seem to figure out where to start with this. Can I setup the api to accept SAML assertions or do I need to request the token in a different way?
You can't use WS-Fed to call a web API. You need OpenID Connect / OAuth as in Calling a web API in a web app using Azure AD and OpenID Connect.
It's for Azure AD but it does illustrate the flow.
What version of ADFS?
If 2.0, there is no OAuth support.
If 3.0, web API only - refer Securing a Web API with ADFS on WS2012 R2 Got Even Easier.
If 4.0, you have the full stack.
Related
I have been trying WITH NO LUCK, to get an embed token to be able to embed my powerbi reports into my existing .netcore web api application. The front end looks like a super easy 1 simple react component that power bi has prepared for me.
But for the backend, I'm literally going in circles.
I got to the point where I decided the cleanest way for me to do this would be through an HTTP Trigger function.
(see this: https://www.taygan.co/blog/2018/05/14/embedded-analytics-with-power-bi )
As an important side note: I DID already grant my application the necessary delegate READ permissions to the power bi Apis)
Another side note, is that I am attempting to do the master user, app owns data approach
Another side note, is that you will see that my link above, the code shows you to get an AAD auth token using a method that is no longer supported (seemingly) by microsoft, so I changed that line of code as you'll see below
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.PowerBI.Api;
using Microsoft.PowerBI.Api.Models;
using Microsoft.Rest;
using Newtonsoft.Json;
namespace PowerBiExample
{
public class EmbedContent
{
public string EmbedToken { get; set; }
public string EmbedUrl { get; set; }
public string ReportId { get; set; }
}
public static class Test
{
private static string tenantId = "this is the id of my entire organization";
static string authorityUrl = $"https://login.microsoftonline.com/{tenantId}";
static string resourceUrl = "https://analysis.windows.net/powerbi/api";
static string apiUrl = "https://api.powerbi.com/";
private static string clientId = "this is the client id of my application that i gave delegate permissions to";
private static string clientSecret = "this is the secret of the application i gave delegate permissions to";
private static string username = "ad master user that i WANTED to sign into power bi with";
private static string password = "that ad users pw";
private static Guid groupId = Guid.Parse("workspaceid in powerbi");
private static Guid reportId = Guid.Parse("report id from within that workspace");
[FunctionName("Test")]
public static async Task<IActionResult> RunAsync(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
{
var credential = new ClientCredential(clientId, clientSecret);
var authenticationContext = new AuthenticationContext(authorityUrl);
// var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, credential);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, credential);
string accessToken = authenticationResult.AccessToken;
var tokenCredentials = new TokenCredentials(accessToken, "Bearer");
using (var client = new PowerBIClient(new Uri(apiUrl), tokenCredentials))
{
// Embed URL
Report report = client.Reports.GetReportInGroup(groupId, reportId);
string embedUrl = report.EmbedUrl;
// Embed Token
var generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view");
EmbedToken embedToken = client.Reports.GenerateTokenInGroup(groupId, reportId, generateTokenRequestParameters);
// JSON Response
EmbedContent data = new EmbedContent();
data.EmbedToken = embedToken.Token;
data.EmbedUrl = embedUrl;
data.ReportId = reportId.ToString();
var s = JsonConvert.SerializeObject(data);
return new JsonResult(s);
// JavaScriptSerializer js = new JavaScriptSerializer();
// string jsonp = "callback(" + js.Serialize(data) + ");";
//
// // Return Response
// return new HttpResponseMessage(HttpStatusCode.OK)
// {
// Content = new StringContent(jsonp, Encoding.UTF8, "application/json")
// };
}
}
}
}
I DO get the Authorization Token returned. I DO NOT get the Embed Token returned. I get unauthorized for that.
Also important note: 1. I also didn't enable Service Principal like it says to do here learn.microsoft.com/en-us/power-bi/enterprise/… (my IT dept said I can't). and 2. they are not an admin or member on the workspace but when I try to add them as member, they are not available. It's an Application, not a user or a group. What should I do
Please check if below points can give an idea to work around.
A fiddler trace may be required to investigate further. The required
permission scope may be missing for the registered application
within Azure AD. Verify the required scope is present within the
app registration for Azure AD within the Azure portal ex: openid ,
profile, offline etc depending on the requirement and the user
logged in.
When using a master user, you'll need to define your app's delegated
permissions ( known as scopes). The master user or Power BI
admin is required to grant consent for using these permissions using
the Power BI REST APIs.
For master users, it is essential to grant permissions from the
Azure portal.
Also check if the group Id provided is correct,
Also For Analysis Services, the master user has to be a
gateway admin.
Your web app uses a user account to authenticate against Azure AD
and get the Azure AD token. The master user needs to have a Power
BI Pro or a Premium Per User (PPU) license.
After successful authentication against Azure AD, your web app will
generate an embed token to allow its users to access specific
Power BI content.
Please go through this considerations to generate embed token
carefully and give proper permissions.
Note: For security reasons, the lifetime of the embed token is set to the remaining lifetime of the Azure AD token used to call the
GenerateToken API.
Therefore, if you use the same Azure AD token to generate several embed tokens, the lifetime of the generated embed tokens will be shorter with each call.
Sometimes that can be the reason for the unauthorized error due to expiry of embed token
Please check this powerbi-docs/embedded-troubleshoot.md at live ·
MicrosoftDocs/powerbi-docs · GitHub to see several issues and
find what is the main issue in your case.
And also check if you need to provide datasetId
Some references:
Unauthorized response on GetReportInGroupAsync PowerBI Embedded
API call using Service Principal - Stack Overflow
Register an app to embed Power BI content in a Power BI embedded
analytics application - Power BI | Microsoft Docs
Understand the permission tokens needed for embedding a Power BI
application - Power BI | Microsoft Docs
I would like to programmatically list and control virtual machines classic (old one) in Azure. For managed it is not problem, there are libraries and the rest API is working, but once I am calling the old API for listing classic, I got 403 (Forbidden).
Is the code fine? Do I need to manage credentials for old API on another place?
My code is here:
static void Main(string[] args)
{
string apiNew = "https://management.azure.com/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxx/providers/Microsoft.Compute/virtualMachines?api-version=2018-06-01";
string apiOld = "https://management.core.windows.net/xxxxxxxxxxxxxxxxxxxxxxxx/services/vmimages"
AzureRestClient client = new AzureRestClient(credentials.TenantId, credentials.ClientId, credentials.ClientSecret);
//OK - I can list the managed VMs.
string resultNew = client.GetRequestAsync(apiNew).Result;
// 403 forbidden
string resultOld = client.GetRequestAsync(apiOld).Result;
}
public class AzureRestClient : IDisposable
{
private readonly HttpClient _client;
public AzureRestClient(string tenantName, string clientId, string clientSecret)
{
_client = CreateClient(tenantName, clientId, clientSecret).Result;
}
private async Task<string> GetAccessToken(string tenantName, string clientId, string clientSecret)
{
string authString = "https://login.microsoftonline.com/" + tenantName;
string resourceUrl = "https://management.core.windows.net/";
var authenticationContext = new AuthenticationContext(authString, false);
var clientCred = new ClientCredential(clientId, clientSecret);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientCred);
var token = authenticationResult.AccessToken;
return token;
}
async Task<HttpClient> CreateClient(string tenantName, string clientId, string clientSecret)
{
string token = await GetAccessToken(tenantName, clientId, clientSecret);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
}
public async Task<string> GetRequestAsync(string url)
{
return await _client.GetStringAsync(url);
}
}
UPDATE 1:
Response details:
HTTP/1.1 403 Forbidden
Content-Length: 288
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 22 Oct 2018 11:03:40 GMT
HTTP/1.1 403 Forbidden
Content-Length: 288
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 22 Oct 2018 11:03:40 GMT
<Error xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Code>ForbiddenError</Code>
<Message>The server failed to authenticate the request.
Verify that the certificate is valid and is associated with this subscription.</Message>
</Error>
Update 2:
I found that same API is used by powershell command Get-AzureVMImage and it is working from powershell. Powershell ask me first to login to Azure with interactive login windows by email and password and the the request use Bearer header to authenticate like mine code.
If I sniff the access token (Bearer header) from communication created by Powershell, I can communicate with that API with success.
Update 3: SOLVED, answer bellow.
1. Reason for 403 when you're calling List VM Images API
It's because your Azure AD registered application is not using the "Windows Azure Service Management API" delegated permissions correctly. I say this because I see your code is acquiring the token directly using application identity (ClientCredential) and not as a user.
Please see the screenshots below. Window Azure Service Management API clearly does not provide any application permissions, only thing that can be used is a delegated permission. If you want to understand more about the difference between the two kinds of permissions, read Permissions in Azure AD. To put it very briefly, when using delegated permissions, the app is delegated permission to act as the signed-in user when making calls to an API. So there has to be a signed-in user.
I was able to reproduce the 403 error using your code and then able to make it work and return a list of classic VM's with some changes. I'll explain the required changes next.
Go to your Azure AD > App registrations > your app > Settings > Required permissions :
2. Changes required to make it work
Change will be to acquire token as a signed in user and not directly using application's clientId and secret. Since your application is a console app, it would make sense to do something like this, which will prompt the user to enter credentials:
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));
Also, since your application is a console application, it would be better to register it as a "Native" application instead of a web application like you have it right now. I say this because console applications or desktop client based applications which can run on user systems are not secure to handle application secrets, so you should not register them as "Web app / API" and not use any secrets in them as it's a security risk.
So overall, 2 changes and you should be good to go. As I said earlier, I have tried these and can see the code working fine and getting a list of classic VMs.
a. Register your application in Azure AD as a native app (i.e. Application Type should be native and not Web app / API), then in required permissions add the "Window Azure Service Management API" and check the delegated permissions as per earlier screenshots in point 1
b. Change the way to acquire token, so that delegated permissions can be used as per the signed in user. Of course, signed in user should have permissions to the VM's you're trying to list or if you have multiple users, the list will reflect those VM's which currently signed in user has access to.
Here is the entire working code after I modified it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http;
using System.Net.Http.Headers;
namespace ListVMsConsoleApp
{
class Program
{
static void Main(string[] args)
{
string tenantId = "xxxxxx";
string clientId = "xxxxxx";
string redirectUri = "https://ListClassicVMsApp";
string apiNew = "https://management.azure.com/subscriptions/xxxxxxxx/providers/Microsoft.Compute/virtualMachines?api-version=2018-06-01";
string apiOld = "https://management.core.windows.net/xxxxxxxx/services/vmimages";
AzureRestClient client = new AzureRestClient(tenantId, clientId, redirectUri);
//OK - I can list the managed VMs.
//string resultNew = client.GetRequestAsync(apiNew).Result;
// 403 forbidden - should work now
string resultOld = client.GetRequestAsync(apiOld).Result;
}
}
public class AzureRestClient
{
private readonly HttpClient _client;
public AzureRestClient(string tenantName, string clientId, string redirectUri)
{
_client = CreateClient(tenantName, clientId, redirectUri).Result;
}
private async Task<string> GetAccessToken(string tenantName, string clientId, string redirectUri)
{
string authString = "https://login.microsoftonline.com/" + tenantName;
string resourceUrl = "https://management.core.windows.net/";
var authenticationContext = new AuthenticationContext(authString, false);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));
return authenticationResult.AccessToken;
}
async Task<HttpClient> CreateClient(string tenantName, string clientId, string redirectUri)
{
string token = await GetAccessToken(tenantName, clientId, redirectUri);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Add("x-ms-version", "2014-02-01");
return client;
}
public async Task<string> GetRequestAsync(string url)
{
return await _client.GetStringAsync(url);
}
}
}
According to the linked documentation you appear to be missing a required request header when requesting the classic REST API
x-ms-version - Required. Specifies the version of the operation to use for this request. This header should be set to 2014-02-01 or higher.
Reference List VM Images: Request Headers
To allow for the inclusion of the header, create an overload for GET requests in the AzureRestClient
public async Task<string> GetRequestAsync(string url, Dictionary<string, string> headers) {
var request = new HttpRequestMessage(HttpMethod.Get, url);
if (headers != null)
foreach (var header in headers) {
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
var response = await _client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
and include the required header when calling apiOld
var headers = new Dictionary<string, string>();
headers["x-ms-version"] = "2014-02-01";
string resultOld = client.GetRequestAsync(apiOld, headers).GetAwaiter().GetResult();
Finnaly I got it to work:
First Open Powershell:
Get-AzurePublishSettingsFile
and save that file.
then type in Powershell
Import-AzurePublishSettingsFile [mypublishsettingsfile]
Open certificate store and find imported certificate. And use that certificate
at the same time with credentials within the HttpClient.
Based on my test, you need to get the access token interactively.
I've perfectly reproduced your issue.
Unfortunately, I didn't get a working source code with the Old API reaching your needs.
Although I've found a Microsoft.ClassicCompute provider, instead of the usual used Microsoft.Compute one, but still failing to have a working test.
I'm pretty sure you should no more "manually" use the old obsolete API, and should use modern Microsoft packages allowing management of Classic and "Normal" elements, like Virtual Machines, or Storage accounts.
The key package is Microsoft.Azure.Management.Compute.Fluent
You can find the documentation here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.management.compute.fluent?view=azure-dotnet
Let me know if you still need help.
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.
I am writing an App server application in C# that needs to access Firebase Database. It uses REST protocol. To authentication i want to use an service account.
Unfortunately there is no library written in C#, so i am trying to put the bellow http Request to work.
I follow this steps:
To get the accesstoken i follow the https://github.com/google/google-api-dotnet-client-samples. The code prints the token so should be ok to that point.
Invoke GET web request passing the token in the access_token query parameter as documented at https://firebase.google.com/docs/reference/rest/database/user-auth.
I tried all variations i could remember, in headers, with apostrophe, APN request style, but always got 401 error or 403. Error code 403 should mean that the API recognize the user but denys access to the resource, but i am not sure if this works this way in this case.
The account is defined in the API console and it has project edit and owner profile, for the Firebase app.
The rules are set like this:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Can't figure out were i went wrong. I don't think i need to go written an JWT token if i use google API library. Rules should not apply to this account so i guess i am not passing the token correctly. By inspecting the token retrieved i can see that it is of type Bear, so i tried to pass it on header with no success too.
Test code:
using System;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Auth.OAuth2;
using System.Threading.Tasks;
using System.Net;
using System.IO;
namespace FirebaseAppServer
{
/// </summary>
public class Program
{
public static void Main(string[] args)
{
accessFirebase();
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
public async static Task accessFirebase()
{
String serviceAccountEmail = "serviceaccount1#myapp.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"App.p12", "notasecret", X509KeyStorageFlags.Exportable); //App2 is the certificate i downloaded from API console
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://www.googleapis.com/auth/firebase.database" //from https://developers.google.com/identity/protocols/googlescopes
,"https://www.googleapis.com/auth/firebase"
,"https://www.googleapis.com/auth/cloud-platform"}
}.FromCertificate(certificate));
var task = await credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None);
Console.WriteLine("AccessToken " + credential.Token.AccessToken); //accessToken has a value, so guess is all good so far.
var request = (HttpWebRequest)WebRequest.Create("https://<Myapp>.firebaseio.com/.json?access_token=" + credential.Token.AccessToken);
request.Method = "GET";
request.ContentType = "application/json";
using (var response = (HttpWebResponse)request.GetResponse()) //Throw error 403 - forbidden
{
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
Console.WriteLine("responseString " + responseString);
}
}
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.