How to create ServiceClientCredential to be used with Microsoft.Azure.Management.Compute - c#

I am trying to programmatically retrieve the HostedServices from Microsoft.Azure.Management.Compute using C#. This requires ServiceClientCredential and I do not know how to get it.
How can I instantiate this class?
I am able to get them using Microsoft.WindowsAzure.Management.Compute but here it returns only the instances under ResourceManager not the classic instances.

First you need to create Active Directory application. See How to: Use the portal to create an Azure AD application and service principal that can access resources
The sample code below uses the nuget package Microsoft.Azure.Management.Compute 13.0.1-prerelease:
public class CustomLoginCredentials : ServiceClientCredentials
{
private string AuthenticationToken { get; set; }
public override void InitializeServiceClient<T>(ServiceClient<T> client)
{
var authenticationContext = new AuthenticationContext("https://login.windows.net/{tenantID}");
var credential = new ClientCredential(clientId: "xxxxx-xxxx-xx-xxxx-xxx", clientSecret: "{clientSecret}");
var result = authenticationContext.AcquireToken(resource: "https://management.core.windows.net/", clientCredential: credential);
if (result == null) throw new InvalidOperationException("Failed to obtain the JWT token");
AuthenticationToken = result.AccessToken;
}
public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null) throw new ArgumentNullException("request");
if (AuthenticationToken == null) throw new InvalidOperationException("Token Provider Cannot Be Null");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AuthenticationToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//request.Version = new Version(apiVersion);
await base.ProcessHttpRequestAsync(request, cancellationToken);
}
}
Then you can initialize the client like this:
netClient = new Microsoft.Azure.Management.Compute.ComputeManagementClient(new CustomLoginCredentials());
netClient.SubscriptionId = _subscriptionId;

The way you'd do this now is to use ITokenProvider and Microsoft.Rest.TokenCredentials.
public class CustomTokenProvider : ITokenProvider
{
private readonly CustomConfiguration _config;
public CustomTokenProvider(CustomConfiguration config)
{
_config = config;
}
public async Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
{
// For app only authentication, we need the specific tenant id in the authority url
var tenantSpecificUrl = $"https://login.microsoftonline.com/{_config.TenantId}/";
// Create a confidential client to authorize the app with the AAD app
IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder
.Create(_config.ClientId)
.WithClientSecret(_config.ClientSecret)
.WithAuthority(tenantSpecificUrl)
.Build();
// Make a client call if Access token is not available in cache
var authenticationResult = await clientApp
.AcquireTokenForClient(new List<string> { _config.Scope })
.ExecuteAsync();
return new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
}
}
And then in your DI configuration
services.AddTransient<IPowerBIClient, PowerBIClient>((provider) =>
{
var config = provider.GetRequiredService<CustomConfiguration>();
var tokenProvider = provider.GetRequiredService<CustomTokenProvider>();
return new PowerBIClient(new Uri(config.BaseUrl), new TokenCredentials(tokenProvider));
});
My example is used with Power BI but would work with anything that needs access to ServiceClientCredentials.
You can use the Nuget package Microsoft.Identity.Client for IConfidentialClientApplication.

A bit later in the game, but this is how we do this in our project. We use the token credentials that is provided by the .net framework to access a managed identity, or visual studio (code) identity, or interactive. And connect to the azure infrastructure API.
internal class CustomTokenProvider : ServiceClientCredentials
{
private const string BearerTokenType = "Bearer";
private TokenCredential _tokenCredential;
private readonly string[] _scopes;
private readonly IMemoryCache _cache;
public CustomTokenProvider(TokenCredential tokenCredential, string[] scopes, IMemoryCache cache)
{
_tokenCredential = tokenCredential ?? throw new ArgumentNullException(nameof(tokenCredential));
_scopes = scopes ?? throw new ArgumentNullException(nameof(scopes));
_cache = cache;
}
public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
var token = await _cache.GetOrCreateAsync("accessToken-tokenProvider." + string.Join("#", _scopes), async e =>
{
var accessToken = await _tokenCredential.GetTokenAsync(new TokenRequestContext(_scopes), cancellationToken);
e.AbsoluteExpiration = accessToken.ExpiresOn;
return accessToken.Token;
});
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(BearerTokenType, token);
await base.ProcessHttpRequestAsync(request, cancellationToken).ConfigureAwait(false);
}
}
Couple of remarks:
The TokenCredential class does not cache tokens and if you don't do it, it will trigger an error at azure due to excessive requests.
Calling a v1 endpoint with v2 calls requires to be a bit creative in the scopes. So when you need to access the management API, provide the following scope "https://management.core.windows.net/.default" and not the user_impersonate scope as specified. This due to some internal conversion on the different endpoints. And '.default' scope is always available and will give yout the on

As #verbedr answered that you can adapt a TokenCredential from the Azure.Identity client library. #antdev answered that you could implement a Microsoft.Rest.ITokenProvider. Another option is to combine both approaches like so:
using Azure.Core;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Rest
{
/// Allows an Azure.Core.TokenCredential to be the Microsoft.Rest.ITokenProvider.
public class TokenCredentialTokenProvider : Microsoft.Rest.ITokenProvider
{
readonly TokenCredential _tokenCredential;
readonly string[] _scopes;
public TokenCredentialTokenProvider(TokenCredential tokenCredential, string[] scopes)
{
_tokenCredential = tokenCredential;
_scopes = scopes;
}
public async Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
{
var accessToken = await _tokenCredential.GetTokenAsync(new TokenRequestContext(_scopes), cancellationToken);
return new AuthenticationHeaderValue("Bearer", accessToken.Token);
}
}
}
It does not have the caching. You could create a CachingTokenProvider or similar if you needed it. This can be used like so:
var tokenCredentials = new Azure.Identity.DefaultAzureCredential(new Azure.Identity.DefaultAzureCredentialOptions
{
AuthorityHost = Azure.Identity.AzureAuthorityHosts.AzurePublicCloud
});
var restTokenProvider = new Microsoft.Rest.TokenCredentialTokenProvider(tokenCredentials,
new string[] { "https://management.core.windows.net/.default" }
);
var restTokenCredentials = new Microsoft.Rest.TokenCredentials(restTokenProvider);
using var computeClient = new ComputeManagementClient(restTokenCredentials);
// computeClient.BaseUri = // set if using another cloud
computeClient.SubscriptionId = subscriptionId;
var vms = computeClient.VirtualMachines.ListAll();
Console.WriteLine("# of vms " + vms.Count());
This worked for me. Here were the relevant dependencies in my csproj that I used:
<PackageReference Include="Azure.Identity" Version="1.4.0" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.23" />
<PackageReference Include="Microsoft.Azure.Management.Compute" Version="46.0.0" />

Related

How to authenticate in Azure Blob Storage through Integrated Windows Authentication?

I need to silently authenticate in Azure Blob Storage from a .NET application running on a Windows machine that is domain-joined and the domain is synced to Azure AD.
I am using this example of authentication flow as the base and trying to adapt it for Blob Storage. I successfully obtain a token from AcquireTokenByIntegratedWindowsAuth() method of PublicClientApplication, but I cannot figure out how to supply it to BlobContainerClient. The most appropriate constructor seems to be the one accepting TokenCredential, but I cannot find a suitable class among descendants of TokenCredential.
I ended up writing my own implementation of TokenCredential:
internal class IwaCredential : TokenCredential
{
private readonly IPublicClientApplication _application;
private readonly string[] _scopes;
public IwaCredential(IPublicClientApplication app, string[] scopes)
{
_application = app;
_scopes = scopes;
}
private async Task<AuthenticationResult> AuthenticateAsync()
{
AuthenticationResult? result = null;
var accounts = await _application.GetAccountsAsync();
if (accounts.Any())
{
try
{
result = await _application.AcquireTokenSilent(_scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
}
}
if (result == null)
{
result = await _application.AcquireTokenByIntegratedWindowsAuth(_scopes).ExecuteAsync();
}
return result;
}
private async Task<AccessToken> GetAccessTokenAsync()
{
var authResult = await AuthenticateAsync();
return new AccessToken(authResult.AccessToken, authResult.ExpiresOn);
}
public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return GetAccessTokenAsync().GetAwaiter().GetResult();
}
public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
return new ValueTask<AccessToken>(GetAccessTokenAsync());
}
}
Then I am able to pass instance of that to the client:
var appOptions = new PublicClientApplicationOptions
{
ClientId = "...",
TenantId = "...",
};
var app = PublicClientApplicationBuilder.CreateWithApplicationOptions(appOptions).Build();
var cred = new IwaCredential(app, new string[] { "https://storage.azure.com/user_impersonation" });
var client = new BlobContainerClient(new Uri("https://foobar.blob.core.windows.net/upload"), cred);
using (Stream file = new FileStream(#"C:\Windows\win.ini", FileMode.Open, FileAccess.Read))
{
var res = await client.UploadBlobAsync("prefix/win.ini", file);
Console.WriteLine(res);
}
It works, but I still feel like I am missing something as I believe there should be support for that flow within the standard library.
Am I doing it right way? Please suggest improvements.
Why not this method using new DefaultAzureCredential(includeInteractiveCredentials: true).
BlobContainerClient blobContainerClient = new BlobContainerClient(
new Uri(#"https://your-blob-uri.blob.core.windows.net/your-container")
, new DefaultAzureCredential(includeInteractiveCredentials: true));
then pass that BlobContainerClient to UploadFile
public static async Task UploadFile
(BlobContainerClient containerClient, string localFilePath)
{
string fileName = Path.GetFileName(localFilePath);
BlobClient blobClient = containerClient.GetBlobClient(fileName);
await blobClient.UploadAsync(localFilePath, true);
}

.net services.AddHttpClient Automatic Access Token Handling

I am trying to write a Blazor app that uses client secret credentials to get an access token for the API. I wanted to encapsulate it in such a way that it handles the token fetching and refreshing behind the scenes. To achieve this, I created the following inherited class which uses IdentityModel Nuget package:
public class MPSHttpClient : HttpClient
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger Logger;
public MPSHttpClient(IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
public async Task<bool> RefreshTokens()
{
if (Tokens.RefreshToken == null)
return false;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
RefreshToken = Tokens.RefreshToken
});
Logger.LogInformation("Refresh Token Result {0}", result.IsError);
if (result.IsError)
{
Logger.LogError("Error: {0)", result.ErrorDescription);
return false;
}
Tokens.RefreshToken = result.RefreshToken;
Tokens.AccessToken = result.AccessToken;
Logger.LogInformation("Access Token: {0}", result.AccessToken);
Logger.LogInformation("Refresh Token: {0}" , result.RefreshToken);
return true;
}
public async Task<bool> CheckTokens()
{
if (await RefreshTokens())
return true;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
public new async Task<HttpResponseMessage> GetAsync(string requestUri)
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
var response = await base.GetAsync(requestUri);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
response = await base.GetAsync(requestUri);
}
}
return response;
}
}
The idea is to keep from having to write a bunch of redundant code to try the API, then request/refresh the token if you are unauthorized. I tried it at first using extension methods to HttpClient, but there was no good way to inject the Configuration into a static class.
So my Service code is written as this:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly MPSHttpClient _httpClient;
public EngineListService(MPSHttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
Everything compiles great. In my Startup, I have the following code:
services.AddScoped<TokenProvider>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Just to be complete, Token Provider looks like this:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
When I run the App, it complains that it can't find a suitable constructor for EngineListService in the call to services.AddHttpClient. Is there a way to pass AddHttpClient an actual instance of the IEngineListService. Any other way I might be able to achieve this?
Thanks,
Jim
I think that EngineListService should not be registered as a HttpClient in services and instead you should register MPSHttpClient.
This follows the "Typed Client" example in the documentation and uses IHttpClientFactory behind the scenes.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#typed-clients
When you use services.AddHttpClient the constructor needs a HttpClient parameter. That is how the HttpClientFactory initializes the HttpClient and then passes it into your service ready to go.
You can change your MPSHttpClient to not inherit HttpClient and instead add a HttpClient parameter to the constructor. You could also have it implement an interface like IMPSHttpClient
public class MPSHttpClient
{
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
HttpClient = httpClient;
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
}
You must remove these lines from MPSHttpClient and use the injected client.
// remove this
var client = new HttpClient();
In Startup add
services.AddHttpClient<MPSHttpClient>(client =>
{
// add any configuration
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Change EngineListService to a normal service registration as it is not a HttpClient
services.AddScoped<IEngineListService, EngineListService>()
Special thanks to #pinkfloydx33 for helping me solve this. This link that he shared https://blog.joaograssi.com/typed-httpclient-with-messagehandler-getting-accesstokens-from-identityserver/ was everything I needed. The trick was that there exists a class called DelegatingHandler that you can inherit and override the OnSendAsync method and do all of your token-checking there before sending it to the final HttpHandler. So my new MPSHttpClient class is as so:
public class MPSHttpClient : DelegatingHandler
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger<MPSHttpClient> Logger;
private readonly HttpClient client;
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger<MPSHttpClient> logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
client = httpClient;
}
public async Task<bool> CheckTokens()
{
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBearerToken(Tokens.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
request.SetBearerToken(Tokens.AccessToken);
response = await base.SendAsync(request, cancellationToken);
}
}
return response;
}
}
The big changes here are the inheritance and I used DI to obtain the HttpClient much like #Rosco mentioned. I had tried to override OnGetAsync in my original version. When inheriting from DelegatingHandler, all you have to override is OnSendAsync. This will handle all of your get, put, post, and deletes from your HttpContext all in one method.
My EngineList Service is written as if there were no tokens to be considered, which was my original goal:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly HttpClient _httpClient;
public EngineListService(HttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
The Token Provider stayed the same. I plan to add expirations and such to it, but it works as is:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
The ConfigureServices code changed just a bit:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<TokenProvider>();
services.AddTransient<MPSHttpClient>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
...
}
You instantiate MPSHttpClient as Transient, then reference it with the AddHttpMessageHandler call attached to the AddHttpClient call. I know this is different than how others implement HttpClients, but I learned this method of creating client services from a Pluralsight video and have been using it for everything. I create a separate Service for each entity in the database. If say I wanted to do tires, I would add the following to ConfigureServices:
services.AddHttpClient<ITireListService, TireListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
It will use the same DelegatingHandler so I can just keep adding services for each entity type while no longer worrying about tokens. Thanks to everyone that responded.
Thanks,
Jim

AuthProvider with Microsoft Graph Api "Ctor not found"

I'm trying to use Microsoft.Graph Api to get a list of users. I am able to authenticate on the api. I receive the Token and basic profile info upon login.
Then I am trying to use the Microsoft.Graph.Auth SDK/nuget found here to generate my requests. Here is a barebone example of what I am trying to do (you can also find this example in the doc of the package.
public void test()
{
var clientApplication = PublicClientApplicationBuilder
.Create(ClientId)
.WithTenantId(TenantId)
.Build();
var authProvider = new IntegratedWindowsAuthenticationProvider(clientApplication);
var graphClient = new GraphServiceClient(authProvider);
var users = await graphClient.Users
.Request()
.GetAsync();
}
But I get the error System.MissingMethodException: 'Method not found: 'Void Microsoft.Graph.Auth.IntegratedWindowsAuthenticationProvider..ctor(Microsoft.Identity.Client.IPublicClientApplication, System.Collections.Generic.IEnumerable`1<System.String>)'.' before even entering the method test(). The message says it cannot find IntegratedWindowsAuthenticationProvider but the package is installed and I can navigate to the constructor (F12) without issue.
If I remove the line with IntegratedWindowsAuthenticationProvider, the code executes without crashing. And I can authenticate into the Api successfully. I tried moving the line after the successful authentication but I get the same error.
Microsoft.Graph.Auth only supports .Net 4.5 and I use 4.8. I cannot downgrade to 4.5 because of other requirements in the codebase. I tried to modify the open source project but I ran into other issues so I decided to not use this package. I implemented a basic solution containing a few methods to manage the token myself.
Here is the class that handles the cached token
using System.IO;
using System.Security.Cryptography;
using Microsoft.Identity.Client;
namespace Overwatch.AutoCAD.Authentication
{
static class TokenCacheHelper
{
static TokenCacheHelper()
{
CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3";
}
/// <summary>
/// Path to the token cache
/// </summary>
public static string CacheFilePath { get; private set; }
private static readonly object FileLock = new object();
public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
null,
DataProtectionScope.CurrentUser)
: null);
}
}
public static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
lock (FileLock)
{
// reflect changesgs in the persistent store
File.WriteAllBytes(CacheFilePath,
ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
null,
DataProtectionScope.CurrentUser)
);
}
}
}
internal static void EnableSerialization(ITokenCache tokenCache)
{
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}
}
}
Here is my modified initial test method
public async void test()
{
AuthenticationResult authResult = await PublicClientApp
.AcquireTokenSilent(scopes, (await PublicClientApp.GetAccountsAsync()).FirstOrDefault())
.ExecuteAsync();
HttpClient client = ApiHelper.CreateHttpClient("https://graph.microsoft.com/v1.0/");
GraphServiceClient graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}));
var users = await graphClient.Users
.Request()
.GetAsync();
}
All you have to do is link the TokenCache helper in the creation of the client app builder
public static void CreateApplication()
{
var builder = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority($"{Instance}{Tenant}")
.WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient");
_clientApp = builder.Build();
TokenCacheHelper.EnableSerialization(_clientApp.UserTokenCache);
}
Of course, add try catches and fail safes based on your requirements.

ADAL.NET ID-Token from logged in user

I have an webapplication which uses the Microsoft Graph API to get data from Office365 services. For the login i took the code from Microsofts sample project, which uses ADAL.NET Library for authentification.
When i make an http request it checks if the a request was authentificated. The problem is, when a new session was started no authetification request was performed, although the user is logged in. I get the error message Error Failed to acquire token silently as no token was found in the cache. Call method AcquireToken.
After research i found out that i have to call the Method AcquireTokenSilentlyAsync(), which verifies if an acceptable token is in the cache. I have implemented this method but it always throws an exception. After debugging i saw that there is no ID-Token when a logged in user makes an request. How can i get this ID-Token?
public static AuthProvider Instance { get; } = new AuthProvider();
// Get an access token. First tries to get the token from the token cache.
public async Task<string> GetUserAccessTokenAsync()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
HttpContextBase httpContextBase = HttpContext.Current.GetOwinContext().Environment["System.Web.HttpContextBase"] as HttpContextBase;
SessionTokenCache tokenCache = new SessionTokenCache(signedInUserID, httpContextBase);
var cachedItems = tokenCache.ReadItems(); // see what's in the cache
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, tokenCache);
ClientCredential clientCredential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
string userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
UserIdentifier userId = new UserIdentifier(userObjectId, UserIdentifierType.UniqueId);
try
{
AuthenticationResult result = await authContext.AcquireTokenSilentAsync(SettingsHelper.GraphResourceId, clientCredential, userId);
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (AdalException ex)
{
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
throw new Exception("Error" + $" {ex.Message}");
}
}
public class SessionTokenCache : TokenCache
{
private HttpContextBase context;
private static readonly object FileLock = new object();
private readonly string CacheId = string.Empty;
public string UserObjectId = string.Empty;
public SessionTokenCache(string userId, HttpContextBase context)
{
this.context = context;
this.UserObjectId = userId;
this.CacheId = UserObjectId + "_TokenCache";
AfterAccess = AfterAccessNotification;
BeforeAccess = BeforeAccessNotification;
Load();
}
public void Load()
{
lock (FileLock)
{
Deserialize((byte[])context.Session[CacheId]);
}
}
public void Persist()
{
lock (FileLock)
{
// Reflect changes in the persistent store.
var bytes = Serialize();
var x = System.Text.Encoding.UTF8.GetString(bytes);
context.Session[CacheId] = Serialize();
// After the write operation takes place, restore the HasStateChanged bit to false.
HasStateChanged = false;
}
}
Please go through the sample which helps in how to use MSAL.NET (Microsoft Authentication Library) to obtain an access token and will fix your issue.
Note:- MSAL is recommended since ADAL is going to be depreciated

How to create role assignments using Azure Active Directory Graph client in C#?

I'm using this library : Microsoft.Azure.ActiveDirectory.GraphClient
class: ActiveDirectoryClient.
I'd like to give an Application (I have the appID) "Owner" access to some subscription. How would I go about doing that? Thanks
The whole premise of this question is incorrect. The GraphClient is not the right client to manage such authorizations. The proper API library for that is Microsoft.Azure.Management.Authorization and the class AuthorizationManagementClient.
I will post additional notes on the actual sequence of calls.
*** Update ***********
As promised here's the sample code:
public static async Task<IServicePrincipal> GetServicePrincipalAsync(string accessToken, string tenantId, string clientId)
{
var graphClient = NewActiveDirectoryClient(accessToken, tenantId);
var matches = await graphClient.ServicePrincipals.Where(sp => sp.AppId == clientId).ExecuteAsync();
return matches.CurrentPage.ToList().FirstOrDefault();
}
private static ActiveDirectoryClient NewActiveDirectoryClient(string accessToken, string tenantId)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
tcs.SetResult(accessToken);
return new ActiveDirectoryClient(
new Uri($"{GraphApiBaseUrl}{tenantId}"),
async () => { return await tcs.Task; });
}
First you need to get the ObjectId of the principal you want to add. In the case of ServicePricipal I have a function that gets it from the directory like so:
Then using that and a scope ("/subscriptions/{my_subscription_id}", for the entire subscription) you can create a RoleAssignment:
public static async Task AssignRoleToPrincipalAsync(
string accessToken,
string subscriptionId,
string scope,
string roleName,
string principalObjectId)
{
using (var client = NewAuthorizationManagementClient(accessToken, subscriptionId))
{
RoleDefinition roleDef = (await FindRoleDefinitionAsync(accessToken, subscriptionId, scope, roleName)).FirstOrDefault();
if (roleDef == null)
throw new Exception($"Role was not found: {roleName}");
var props = new RoleAssignmentProperties()
{
PrincipalId = principalObjectId,
RoleDefinitionId = roleDef.Id
};
await client.RoleAssignments.CreateAsync(scope, Guid.NewGuid().ToString("N"), props);
}
}
private static AuthorizationManagementClient NewAuthorizationManagementClient(string accessToken, string subscriptionId)
{
return new AuthorizationManagementClient(new TokenCredentials(accessToken)) { SubscriptionId = subscriptionId};
}
***** Update *****
To get the token using Azure.Identity you can use the following snippet
var accessToken = await new AzureCliCredential().GetTokenAsync(
new TokenRequestContext(new[] { "https://management.azure.com/.default" }));
var client = new AuthorizationManagementClient(
new TokenCredentials(accessToken.Token))
{
SubscriptionId = subscription.Data.SubscriptionId
};

Categories