Azure Storage Management Client authentication failure - c#

Using the Azure management APIs to access a storage account I use TokenCloudCredentials for authentication. This works fine for storage accounts under Resource Management but not for Classic storage accounts.
When trying to execute any method on a classic storage account client using TokenCloudCredentials I received this error message:
ForbiddenError: The server failed to authenticate the request. Verify
that the certificate is valid and associated with this subscription.
Now for the code-
Working method for Azure Resource Manager
var clientId = "{clientID}";
var tenant = "{tenant GUID}";
var pw = "{password}";
var authenticationContext = new AuthenticationContext("https://login.windows.net/" + tenant);
var credential = new ClientCredential(clientId , pw);
Task<AuthenticationResult> tskGetToken = authenticationContext.AcquireTokenAsync(resource: "https://management.core.windows.net/", credential);
AuthenticationResult token = tskGetToken.Result;
SubscriptionCloudCredentials creds = new TokenCloudCredentials("{subscription id}", token.AccessToken);
StorageManagementClient smc = new StorageManagementClient(creds);
Task<StorageAccountListKeysResponse> tskTargetKeysSource = smc.StorageAccounts.ListKeysAsync("{resource group}", "{storage account name");
while (tskTargetKeysSource.Status != TaskStatus.RanToCompletion)
{
if (tskTargetKeysSource.Exception != null)
throw tskTargetKeysSource.Exception;
Console.WriteLine("Running - Getting target storage account Storage Key");
Thread.Sleep(2500);
}
This works and I receive the storage keys back.
Broken method for Azure Classic Storage:
var clientId = "{clientID}";
var tenant = "{tenant GUID}";
var pw = "{password}";
var authenticationContext = new AuthenticationContext("https://login.windows.net/" + tenant);
var credential = new ClientCredential(clientId , pw);
Task<AuthenticationResult> tskGetToken = authenticationContext.AcquireTokenAsync(resource: "https://management.core.windows.net/", credential);
AuthenticationResult token = tskGetToken.Result;
SubscriptionCloudCredentials creds = new TokenCloudCredentials("{subscription id}", token.AccessToken);
Microsoft.WindowsAzure.Management.Storage.StorageManagementClient classicSmc = new Microsoft.WindowsAzure.Management.Storage.StorageManagementClient(creds);
Task<StorageAccountGetKeysResponse> tskSourceKeysSource = classicSmc.StorageAccounts.GetKeysAsync("{storage account name}", new CancellationToken());
while (tskSourceKeysSource.Status != TaskStatus.RanToCompletion)
{
if (tskSourceKeysSource.Exception != null)
throw tskSourceKeysSource.Exception; // Exception thrown here
Console.WriteLine("Running - Getting source storage account Storage Key");
Thread.Sleep(2500);
}
I am not sure what the difference is. The application that I am writing has proper permission in Azure Active directory and it has permissions on the appropriate resources (storage accounts, resource groups etc.). These operations are using the same subscription as well.

Related

Unable to authorize Azure LogAnalytics Workspace

I am trying to connect to my workspace in the Azure Portal. I am getting the error as
Operation returned an invalid status code 'Unauthorized'.
The creds object has fetched the Authentication Token and I have added resource permissions to my app as mentioned in this link
using System;
using Microsoft.Azure.OperationalInsights;
using Microsoft.Rest.Azure.Authentication;
namespace LogAnalytics
{
class Program
{
static void Main(string[] args)
{
var workspaceId = "**myworkspaceId**";
var clientId = "**myClientId**";
var clientSecret = "**myClientSecret**";
//<your AAD domain>
var domain = "**myDomain**";
var authEndpoint = "https://login.microsoftonline.com";
var tokenAudience = "https://api.loganalytics.io/";
var adSettings = new ActiveDirectoryServiceSettings
{
AuthenticationEndpoint = new Uri(authEndpoint),
TokenAudience = new Uri(tokenAudience),
ValidateAuthority = true
};
var creds = ApplicationTokenProvider.LoginSilentAsync(domain,clientId, clientSecret,
strong textadSettings).GetAwaiter().GetResult();
var client = new OperationalInsightsDataClient(creds);
client.WorkspaceId = workspaceId;
//Error happens below
var results = client.Query("union * | take 5");
Console.WriteLine(results);
Console.ReadLine();
}
}
}
Operation returned an invalid status code 'Unauthorized'.
According to the error message and the code you provided, you need to add permission in your registered application in Azure AD.
Note: If you want to add permission to application you need to be admin, and then you could use the ClientId and ClientSecret to get Authentication Token and read log analytics.
However, if you are not admin, you could delegate permission to user and access to Azure AD with username and password.
To get authentication token with user, you could can use the function UserTokenProvider.LoginSilentAsync(nativeClientAppClientid, domainName, userName, password).GetAwaiter().GetResult() to get our credentials.

Azure - Token is not renewed (Authentication_ExpiredToken). Why?

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?

How to export azure database to blob storage

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.

Azure - Programmatically Create Storage Account

I have tried the following code to create a new storage account in Azure:
Getting the token (success - I received a token):
var cc = new ClientCredential("clientId", "clientSecret");
var context = new AuthenticationContext("https://login.windows.net/subscription");
var result = context.AcquireTokenAsync("https://management.azure.com/", cc);
Create cloud storage credentials:
var credential = new TokenCloudCredentials("subscription", token);
Create the cloud storage account (fails):
using (var storageClient = new StorageManagementClient(credentials))
{
await storageClient.StorageAccounts.CreateAsync(new StorageAccountCreateParameters
{
Label = "samplestorageaccount",
Location = LocationNames.NorthEurope,
Name = "myteststorage",
AccountType = "RA-GRS"
});
}
Error:
ForbiddenError: The server failed to authenticate the request. Verify
that the certificate is valid and is associated with this
subscription.
I am not sure if this is one of those misleading messages or if I misconfigured something in Azure?
As far as I know, Azure provides two types of storage management library now.
Microsoft.Azure.Management.Storage
Microsoft.WindowsAzure.Management.Storage
Microsoft.Azure.Management.Storage is used to create new ARM storage.
Microsoft.WindowsAzure.Management.Storage is used to create classic ARM storage.
I guess you want to create the new arm storage but you used the "Microsoft.WindowsAzure.Management.Storage" library. Since the "Microsoft.WindowsAzure.Management.Storage" uses the certificate to auth requests, you will get the error. If you want to know how to use "Microsoft.WindowsAzure.Management.Storage" to create classic storage, I suggest you refer to this article.
I assume you want to create new ARM storage, I suggest you install the "Microsoft.Azure.Management.Storage" Nuget package.
More details, you could refer to the following code.
static void Main(string[] args)
{
var subscriptionId = "your subscriptionId";
var clientId = "your client id";
var tenantId = "your tenantid";
var secretKey = "secretKey";
StorageManagementClient StorageManagement = new StorageManagementClient(new Microsoft.Azure.TokenCloudCredentials(subscriptionId, GetAccessToken(tenantId, clientId, secretKey)));
var re= StorageManagement.StorageAccounts.CreateAsync("groupname", "sotrage name",new Microsoft.Azure.Management.Storage.Models.StorageAccountCreateParameters() {
Location = LocationNames.NorthEurope,
AccountType = Microsoft.Azure.Management.Storage.Models.AccountType.PremiumLRS
},new CancellationToken() { }).Result;
Console.ReadKey();
}
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;
}

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