I'm working on an application to obtain data through
OAuth of Dynamics 365
to do this use the following example
https://www.youtube.com/watch?v=Td7Bk3IXJ9s
public static async Task Auth()
{
string URL = "https://grupolg.api.crm.dynamics.com/api/data/v9.1/";
AuthenticationParameters API = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(URL)).Result;
//APP KEY, SECRET KEY
ClientCredential Creds = new ClientCredential("hidden for security", "hidden for security");
AuthenticationContext authContext = new AuthenticationContext(API.Authority);
string token = authContext.AcquireTokenAsync(API.Resource, Creds).Result.AccessToken;
using (HttpClient httpClient = new HttpClient())
{
httpClient.Timeout = new TimeSpan(0,2,0);
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",token);
HttpResponseMessage res = await httpClient.GetAsync(URL+"/contacts$top=1");
}
}
but I get the following error:
The error is caused by the ADAL version you're using and how the authority URL is generated.
If you downgrade the Microsoft.IdentityModel.Clients.ActiveDirectory library version to 3.9.18 you'll be able to connect without problems. There's an open bug in GitHub where you can track its progress or comment to add more information to get a definitive fix for it.
Related
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 have a C# Azure Web Application that accesses Azure Active Directory to get data from users and groups.
After 1h of activity, I get the following error:
{"odata.error":{"code":"Authentication_ExpiredToken","message":{"lang":"en","value":"Your access token has expired. Please renew it before submitting the request."},"date":"2018-03-16T16:17:59","requestId":"cfa18a20-3c2c-4806-ac36-9d4e9ba7738c","values":null}}
Here is my method to get a ActiveDirectoryClient:
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.Azure.ActiveDirectory.GraphClient.Extensions;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
private ActiveDirectoryClient CreateGraphClient()
{
string tenantId = WebConfigurationManager.AppSettings["ida:TenantId"],
applicationId = WebConfigurationManager.AppSettings["ida:ClientId"],
secret = WebConfigurationManager.AppSettings["ida:ClientSecret"],
authority = WebConfigurationManager.AppSettings["ida:AADInstance"] + tenantId,
resrouce = "https://graph.windows.net",
token = string.Empty;
ClientCredential credential = new ClientCredential(applicationId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
try
{
token = authContext.AcquireTokenAsync(resrouce, credential).Result.AccessToken;
}
catch (Exception)
{
token = authContext.AcquireTokenSilentAsync(resrouce, credential.ClientId).Result.AccessToken;
}
Uri baseServiceUri = new Uri(resrouce);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(new Uri(baseServiceUri, tenantId), async () => await Task.FromResult(token));
return activeDirectoryClient;
}
I think the server should refresh the token but it's not happenning. I'm using ADAL 3.x so the 'refresh_token' doesn't exist anymore (I guess).
I know this try/catch is not the way but it was a test.
Any help?
I need to know exactly how to login to Azure, using c#.
I basically want to do this, but from the code:
]a link](https://learn.microsoft.com/en-us/azure/sql-database/sql-database-export)
Here is the code I copied from the internet trying to achieve this:
But I don't know how to generate the token.
SqlManagementClient managementClient = new SqlManagementClient(new TokenCloudCredentials(subscriptionId, GetAccessToken(tenantId, clientId, secretKey)));
var exportParams = new DacExportParameters()
{
BlobCredentials = new DacExportParameters.BlobCredentialsParameter()
{
StorageAccessKey = storageKey,
Uri = new Uri(baseStorageUri)
},
ConnectionInfo = new DacExportParameters.ConnectionInfoParameter()
{
ServerName = azureSqlServer,
DatabaseName = azureSqlDatabase,
UserName = adminLogin,
Password = adminPassword
}
};
var exportResult = managementClient.Dac.Export(azureSqlServerName, exportParams);
I have a GetToken function, but I have no idea where to take the
tenant + client id + secret
private static string GetAccessToken(string tenantId, string
clientId, string secretKey)
{
var authenticationContext = new
AuthenticationContext($"https://login.windows.net/{tenantId}");
var credential = new ClientCredential(clientId, secretKey);
var result =authenticationContext
.AcquireTokenAsync("https://management.core.windows.net/",
credential);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
var token = result.Result.AccessToken;
return token;
}
This question was asked before
Azure Database export with C#
but I need to see the actual code and explanation on how to get the connection info.
I need to see the actual code and explanation on how to get the connection info.
I would recommend you follow this tutorial about registering your AAD application and adding the secret key. Moreover, you could also follow Using the Azure ARM REST API – Get Access Token.
SqlManagementClient managementClient = new SqlManagementClient(new TokenCloudCredentials(subscriptionId, GetAccessToken(tenantId, clientId, secretKey)));
Based on your code, I assumed that you are using the package Microsoft.WindowsAzure.Management.Sql, if you use the TokenCloudCredentials, you may receive the following error response:
AFAIK, Microsoft.WindowsAzure.Management.Libraries requires the X509Certificate2 authentication, you need to construct the CertificateCloudCredentials for your SqlManagementClient. For uploading a management certificate under your subscription, you could follow Upload an Azure Service Management Certificate. For retrieving the X509Certificate2 instance, you could follow the code snippet under the Authenticate using a management certificate section from here.
For token-based authentication, you could use the package Microsoft.Azure.Management.Sql and construct your SqlManagementClient as follows:
var sqlManagement = new SqlManagementClient(new TokenCredentials("{access-token}"));
Moreover, you need to change the resource from https://management.core.windows.net/ to https://management.azure.com/ when invoking the AcquireTokenAsync method.
EDIT 10/24
I think this was all likely user error - see my answer below for remedy before getting too deep into this question
TL;DR:
For my OAuth 2.0 code flow...
Why does my TokenCredentials not work with my AutoRest client?
I'm getting NO bearer token applied to the request / no Authorization header set
I know my pipeline works already..
Using code from this azure sample, which is NOT an AutoRest client, I can successfully get my access_token and can get JSON from my protected Web API project.. so I've ruled out all the prerequisite stuff.. I know my pipeline works
My AutoRest setup..
1.) Downloaded from GitHub this AutoRest repo v1.1.0
2.) Downloaded my swagger JSON to disk, saved as swagger.json
3.) Ran this command-line to generate C# files:
autorest --input-file=swagger.json --csharp --output-folder=MyCorp_ApiClient_Tsl --namespace='MyCorp.ApiClient' --add-credentials
4.) Copied generated classes into my .NET 4.6.2 web site
5.) These are my NuGets:
- Microsoft.Rest.ClientRuntime version="2.3.8"
- Microsoft.Rest.ClientRuntime.Azure.Authentication version="2.3.1"
- Microsoft.IdentityModel.Clients.ActiveDirectory version="2.28.3"
Here's what's not working:
AdalTokenHelper tokenHelper = new AdalTokenHelper();//helper code further below
string token = await tokenHelper.GetTokenString();
var svcClientCreds = new TokenCredentials(token, "Bearer");
client = new MyCorp.ApiClient(new Uri(apiRsrcUrl), svcClientCreds,
new DelegatingHandler[] { new MyAzureTracingHandler() });
//make call to OData controller...
MyCorp.ApiClient.Models.ODataResponseListStatus statusList = await client.Status.GetStatusAsync(expand: "StatusType",cancellationToken: defaultCancelThreadToken);
return View(statusList.Value);
I've tried variations of the above, using different ctor's of TokenCredentials, but no matter, I can put my breakpoint in MyAzureTracingHandler and see the request has no Authorization headers applied.. so I get the expected 401 Unauthorized response.
If I modify MyAzureTracingHandler to accept my instance of TokenCredentials then I can force the request to have the appropriate bearer token applied..
This works, but, feels hack-ish:
I changed my original client instantiation snippet from this:
client = new ApiClient(new Uri(apiRsrcUrl), svcClientCreds,
new DelegatingHandler[] { new MyAzureTracingHandler() });
To this:
client = new ApiClient(new Uri(apiRsrcUrl), svcClientCreds,
new DelegatingHandler[] { new MyAzureTracingHandler(svcClientCreds) });
And inside the SendAsync method of MyAzureTracingHander I do this:
await svcClientCreds.ProcessHttpRequestAsync(request, cancellationToken);
Am I doing something wrong? I don't think I should have to pass the ServiceClientCredentials in twice when instantiating my client.
Appendix A - Getting access token via ADAL:
private string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private string tslResourceID = ConfigurationManager.AppSettings["ross:TslWebApiResourceId"];
private static string loginRedirectUri = ConfigurationManager.AppSettings["ross:LoginRedirectUri"];
private AuthenticationContext authContext;
private AuthenticationResult authenticationResult;
public async Task<string> GetTokenString()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
try
{
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(clientId, appKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
authContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(userObjectID));
UserIdentifier userIdentifier = new UserIdentifier(userObjectID, UserIdentifierType.UniqueId);
authenticationResult = await authContext.AcquireTokenSilentAsync(tslResourceID, clientcred, userIdentifier);
}
catch(AdalException ex)
{
throw ex;
}
return authenticationResult.AccessToken;
}
While I believe I ran my autorest command with --add-credentials it's possible I may have used the older syntax... --AddCredentials true
I also did not run autorest --reset as the docs recommend you do
One of these is the culprit, because now my 1.1.0 autorest installation is generating everything correctly.
I am creating demo app that consumes sharepoint REST API. I'm able to successfully retrieve Bearer token (by client ID and certificate). App is registered in Azure AD and all Sharepoint permissions there are checked. I am able to retrieve all lists for example, but I am not able to retrieve a file. The same query works in browser. I assume it is permission problem. Do I have to register this app in SP? https://tenant.sharepoint.com/IT/_layouts/15/appinv.aspx I tried it without success, but I think permissions in Azure AD should be sufficient. My GET query returns
{"error":{"code":"-2130575338, Microsoft.SharePoint.SPException","message":{"lang":"en-US","value":"The file /IT/vystupnidokumentydoc/filename.docx does not exist."}}}
Same query is OK in browser in user context.
REST call:
private async static Task DoStuffInOffice365(string token)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
client.DefaultRequestHeaders.Add("Accept", "application / json; odata = verbose");
string url;
url = "https://tenant.sharepoint.com/_api/web/GetFileByServerRelativeUrl('/IT/vystupnidokumentydoc/filename.docx')";
using (HttpResponseMessage response = await client.GetAsync(url))
{
var contents = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
Console.WriteLine("Fail!");
else
Console.WriteLine("OK.");
}
}
Get token:
private async static Task<string> GetAccessToken()
{
//authentication context
string authority = "https://login.windows.net/tenant.onmicrosoft.com/";
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
var certPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
certPath = certPath.Substring(0, certPath.LastIndexOf('\\')) + $"\\{CERT_FILE}";
var certfile = System.IO.File.OpenRead(certPath);
var certificateBytes = new byte[certfile.Length];
certfile.Read(certificateBytes, 0, (int)certfile.Length);
var cert = new X509Certificate2(
certificateBytes,
PRIVATE_KEY_PASSWORD,
X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet);
ClientAssertionCertificate cac = new ClientAssertionCertificate(CLIENT_ID, cert);
var authenticationResult = await authenticationContext.AcquireTokenAsync(P_URL, cac);
return token = authenticationResult.AccessToken;
}
Based on the error message, it seems the file was not found on the location provided. Please ensure the file exits on the site.
Do I have to register this app in SP?
If you were developing SharePoint add-in, yes. And you can refer here for the authentication/authorization for the SharePoint add-in.
If you were not developing an SharePoint add-ins, we also can use the Microsoft Graph-GetItem to get the item and download the drive item through this REST.