Using authentication token in azure sdk fluent - c#

To authenticate with Azure in azure sdk fluent nuget, there is a method that uses client id and secret as below
var azureCredentials = new AzureCredentials(new
ServicePrincipalLoginInformation
{
ClientId = "ClientId",
ClientSecret = "ClientSecret"
}, "tenantId", AzureEnvironment.AzureGlobalCloud);
Is there any interface where authentication token (JWT) can be used instead of using client id and secret while creating IAzure in the below code?
_azure = Azure
.Configure()
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.Authenticate(azureCredentials)
.WithSubscription(_subscriptionId);
Note: I have a separate authenticater module that keeps client id and secret with itself and uses them to get authentication token which will be used by other components/sdks.

The answer is actually yes, you can use the authentication token (JWT). It's just not that obvious.
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId, false);
var result = context.AcquireToken("https://management.core.windows.net/", clientId, new Uri("http://localhost"), PromptBehavior.Always);
var token = result.AccessToken;
var client = RestClient
.Configure()
.WithEnvironment(AzureEnvironment.AzureGlobalCloud)
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.WithCredentials(new TokenCredentials(token))
.Build();
var azure = Azure
.Authenticate(client, tenantId)
.WithSubscription(subscriptionId);
Sigh...they've changed the WithCredentials to use an AzureCredentials instead of a ServiceClientCredentials. Here's an updated version:-
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId, false);
var result = context.AcquireToken("https://management.core.windows.net/", clientId, new Uri("http://localhost"), PromptBehavior.Always);
var token = result.AccessToken;
var tokenCredentials = new TokenCredentials(token);
var azureCredentials = new AzureCredentials(
tokenCredentials,
tokenCredentials,
tenantId,
AzureEnvironment.AzureGlobalCloud);
var client = RestClient
.Configure()
.WithEnvironment(AzureEnvironment.AzureGlobalCloud)
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.WithCredentials(azureCredentials)
.Build();
var azure = Azure
.Authenticate(client, tenantId)
.WithSubscription(subscriptionId);

Starting from Azure Management Fluent SDK v1.10 you can use any credentials provider that is derived from ServiceClientCredentials. In other words you should be able to pass already acquired Bearer token string to AzureCredentials constructor like this
var customTokenProvider = new AzureCredentials(
new TokenCredentials(armAuthToken),
new TokenCredentials(graphAuthToken),
tenantId,
AzureEnvironment.AzureGlobalCloud);
var client = RestClient
.Configure()
.WithEnvironment(AzureEnvironment.AzureGlobalCloud)
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.WithCredentials(customTokenProvider)
.Build();
var authenticatedClient = Azure.Authenticate(client, tenantId);

Is there any interface where authentication token (JWT) can be used instead of using client id and secret while creating IAzure in the below code?
In short no. Base on my experence, if we want to access the corresponding Azure resources then we need to get the authentication token (JWT) from the corresponding resources. Azure has lots of resources such as AzureSQL, KeyVault, ResourceManagement etc.
On my option, it is not make senses that use the authentication token (JWT) that can access all of the Azure Resources.
Currently, we could get AzureCredentials from file, ServicePrincipalLoginInformation, UserLoginInformation
If we want to operate a certain resource, then we could use authentication token (JWT) to do, take KeyVault as example.
public static async Task<string> GetAccessToken(string azureTenantId,string azureAppId,string azureSecretKey)
{
var context = new AuthenticationContext("https://login.windows.net/" + azureTenantId);
ClientCredential clientCredential = new ClientCredential(azureAppId, azureSecretKey);
var tokenResponse =await context.AcquireTokenAsync("https://vault.azure.net", clientCredential); //KeyVault resource : https://vault.azure.net
var accessToken = tokenResponse.AccessToken;
return accessToken;
}
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessToken));

Related

How do I use Service Principal authentication with an Azure Machine Learning Pipeline Endpoint in C#?

I'm trying to call an Azure Machine Learning Pipeline Endpoint I've set up using C# & the Machine Learning REST api.
I am certain that I have the Service Principal configured correctly, as I can successfully authenticate & hit the endpoint using the azureml-core python sdk:
sp = ServicePrincipalAuthentication(
tenant_id=tenant_id,
service_principal_id=service_principal_id,
service_principal_password=service_principal_password)
ws =Workspace.get(
name=workspace_name,
resource_group=resource_group,
subscription_id=subscription_id,
auth=sp)
endpoint = PipelineEndpoint.get(ws, name='MyEndpoint')
endpoint.submit('Test_Experiment')
I'm using the following example in C# to attempt to run my endpoint: https://learn.microsoft.com/en-us/azure/machine-learning/how-to-deploy-pipelines#run-a-published-pipeline-using-c
I'm attempting to fill auth_key with the following code:
var clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
var cred = new ClientSecretCredential(tenantId, clientId, clientSecret);
var auth_key = cred.GetToken(new Azure.Core.TokenRequestContext(new string[] {".default" }));
I receive a 401 (unauthorized).
What am I am doing wrong?
UPDATE *
I changed the 'scopes' param in the TokenRequestContext to look like:
var auth_key = cred.GetToken(new Azure.Core.TokenRequestContext(new string[] { "http://DataTriggerApp/.default" }));
http://DataTriggerApp is one of the servicePrincipalNames that shows up when i query my Service Principal from the azure CLI.
Now, when I attempt to use the returned token to call the Machine Learning Pipeline Endpoint, I receive a 403 instead of a 401. Maybe some progress?
Ok, through a lot of trial-and-error I was able to come up with two ways of acquiring a token that allows me to hit my Azure Machine Learning Pipeline Endpoint through the REST api. One uses Microsoft.Identity.Client & one uses Azure.Identity.
using Microsoft.Identity.Client;
...
public static async Task<string> GetAccessToken()
{
var clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
.Build();
var result = await app.AcquireTokenForClient(new string[] { "https://ml.azure.com/.default" }).ExecuteAsync();
return result.AccessToken;
}
Or:
using Azure.Identity;
...
public static async Task<string> GetAccessToken()
{
var clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
var cred = new ClientSecretCredential(tenantId, clientId, clientSecret);
var token = await cred.GetTokenAsync(new Azure.Core.TokenRequestContext(new string[] { "https://ml.azure.com/.default" }));
return token.Token;
}

Get Token from Azure using AAD App (ClientID, TenantID, Cert-Thumbprint)

I have below Method to get a token from Azure using ClientID, TenantID and AADAppPassword
This is working awesome but now I need to switch to different AAD AppID and use Certificate Thumbprint Or Certificate pfx.
I don't want to change my 1000+ lines of code.
Can someone help me get a token the same way I'm getting using below Method but use Certificate Thumbprint instead and which returns token so that I can call the method right before I'm about to make rest API call.
public static async Task<string> GetAccessToken(string tenantId, string clientId, string clientKey)
{
string authContextURL = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextURL);
var credential = new ClientCredential(clientId, clientKey);
var result = await authenticationContext
.AcquireTokenAsync("https://management.azure.com/", credential);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
You must use ClientAssertionCertificate instead of ClientCredential
X509Certificate2 cert = ReadCertificateFromStore(config.CertName);
certCred = new ClientAssertionCertificate(config.ClientId, cert);
result = await authContext.AcquireTokenAsync(todoListResourceId, certCred);
You may refer the Azure AD v1 Sample for this.
MSAL.NET is now the recommended auth library to use with the Microsoft identity platform. No new features will be implemented on ADAL.NET. The efforts are focused on improving MSAL. You can refer the documentation here if you are planning to migrate applications to MSAL.NET

Azure AD B2C with WebApi2- calling GraphAPI after authentication

I have a WebApi2 app which servers as api for my app frontend. Now i want to use AD B2C to manage my users - let's say I want to differentiate them by their roles (admin or customer) and for that i created two b2c users groups accordingly. When user logs in i want to display different things for users with different roles (groups).
I'm using this example to setup Startup.Auth.cs in my WebApi2 project:
var tvps = new TokenValidationParameters
{
ValidAudience = clientId,
AuthenticationType = signUpSignInPolicy,
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, tenant, defaultPolicy))),
});
From what I have read b2c doesn't return user's grups in claims for now. Some people suggested I need to call GraphApi after obtaining token to fetch these groups and add them to user's claims:
private static async Task<string> GetGroups(string token, string userId)
{
using (var client = new HttpClient())
{
var requestUrl = $"https://graph.microsoft.com/v1.0/users/{userId}/memberOf?$select=displayName";
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
This is where I've stuck. How can I inject my code to get token for calling graph? I've messed with OAuthBearerAuthenticationOptions.Provider:
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = (context) =>
{
// var token = ??
// var userId = <get from context's claims>
// var groups = GetGroups(token, userId);
// <add to claims>
return Task.CompletedTask;
}
},
...but I don't know how to get to token. Maybe that's wrong from the start and I need another approach?
Customer's token cannot be used to call AADGraph/MSGraph Apis. To get token to call graph apis in an automated way, we need app-only access. We need to configre an app in the tenant, the crendetial of which are used to get a token. That token can then be used to call memberOF Api (or any other api which does or require user information to be there)
Here is the sample and explaination of how to call AAD Graph apis in a B2C dependent service.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet

C# Get Access Token for Azure AD Identity

I try to get an access token for an identity to get data from all users profiles. I'm using OpenID connect to authenticate the user, in which I succeeded. I'm also able to get an access token, but it is not valid.
The code I'm using:
To authenticate:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
ClientId = AppVar.ClientId,
ClientSecret = AppVar.ClientSecret,
Authority = AppVar.AzureADAuthority,
RedirectUri = "https://localhost:44326/",
ResponseType = "code id_token",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) => {
var code = context.Code;
ClientCredential credential = new ClientCredential(AppVar.ClientId, AppVar.ClientSecret);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
ADALTokenCache cache = new ADALTokenCache(signedInUserID);
AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantID), cache);
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, AppVar.AzureResource);
return Task.FromResult(0);
}
}
});
To acquire an access token for https://graph.microsoft.com
public ActionResult Index()
{
string usrObjectId = ClaimsPrincipal.Current.FindFirst(AppVar.ClaimTypeObjectIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(AppVar.AzureADAuthority, new ADALTokenCache(usrObjectId));
ClientCredential credential = new ClientCredential(AppVar.ClientId, AppVar.ClientSecret);
AuthenticationResult res = authContext.AcquireToken(AppVar.AzureResource, credential);
var client = new RestClient("https://graph.microsoft.com/v1.0/users/?$select=userPrincipalName,displayName,mobilePhone");
var request = new RestRequest(Method.GET);
request.AddHeader("Cache-Control", "no-cache");
request.AddHeader("Authorization", "Bearer " + res.AccessToken);
IRestResponse response = client.Execute(request);
return View();
}
But when I execute the request, I get:
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "1cc9e532-bd31-4ca5-8f1d-2d0796883c2e",
"date": "2018-10-17T06:50:35"
}
}
}
What am I doing wrong?
I had the same issue. Use following code which I have used to get the Access Token from Azure AD. Just Login to your Azure portal and find your Tenant ID and Client ID and paste it to the following code. It works perfectly for me.
namespace TokenGenerator
{
class Program
{
private static string token = string.Empty;
static void Main(string[] args)
{
//Get an authentication access token
token = GetToken();
}
#region Get an authentication access token
private static string GetToken()
{
// TODO: Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612
// and add using Microsoft.IdentityModel.Clients.ActiveDirectory
//The client id that Azure AD created when you registered your client app.
string clientID = "Your client ID";
string AuthEndPoint = "https://login.microsoftonline.com/{0}/oauth2/token";
string TenantId = "Your Tenant ID";
//RedirectUri you used when you register your app.
//For a client app, a redirect uri gives Azure AD more details on the application that it will authenticate.
// You can use this redirect uri for your client app
string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
//Resource Uri for Power BI API
string resourceUri = "https://analysis.windows.net/powerbi/api";
//Get access token:
// To call a Power BI REST operation, create an instance of AuthenticationContext and call AcquireToken
// AuthenticationContext is part of the Active Directory Authentication Library NuGet package
// To install the Active Directory Authentication Library NuGet package in Visual Studio,
// run "Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory" from the nuget Package Manager Console.
// AcquireToken will acquire an Azure access token
// Call AcquireToken to get an Azure token from Azure Active Directory token issuance endpoint
string authority = string.Format(CultureInfo.InvariantCulture, AuthEndPoint, TenantId);
AuthenticationContext authContext = new AuthenticationContext(authority);
string token = authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto)).Result.AccessToken;
Console.WriteLine(token);
Console.ReadLine();
return token;
}
#endregion
}
}
Looking at your error, since it's failing at token validation my guess would be that it's related to audience for which the token was acquired.
You're calling the https://graph.microsoft.com endpoint, so make sure that is the exact value for the resource.
Specifically in this code, make sure AppVar.AzureResource gets a value https://graph.microsoft.com
AuthenticationResult res = authContext.AcquireToken(AppVar.AzureResource, credential);
var client = new RestClient("https://graph.microsoft.com/v1.0/users/?$select=userPrincipalName,displayName,mobilePhone");
var request = new RestRequest(Method.GET);

Microsoft Graph API Scripted Authentication

How does one authenticate as a user without any direct user interaction?
Otherwise i found a workaround with client credential flow in this example : https://github.com/microsoftgraph/console-csharp-snippets-sample
but if i try to implement this code in an c# Asp.net mav applcition or a windows forms application i cant get an application token. If i debug the app it stuck at waiting for token but it doesn't throw an error (virus protection already deactivated). Does anyone have a idea for the main problem or my workaround?
This is the Code for my workaround trying to get a token, but stuck on daemonClient.AcquireTokenForClientAsync.
public async Task<Users> GetUser(string Username)
{
MSALCache appTokenCache = new MSALCache(clientId);
ClientCredential clientdummy = new ClientCredential(clientSecret);
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(clientId, string.Format(AuthorityFormat, tenantId), redirectUri,
clientdummy, null, null);
authenticate(daemonClient).Wait();
string token = authResult.AccessToken;
client = GetAuthenticatedClientForApp(token);
IGraphServiceUsersCollectionPage users = client.Users.Request().GetAsync().Result;
}
private async Task<AuthenticationResult> authenticate(ConfidentialClientApplication daemonClient)
{
authResult = await daemonClient.AcquireTokenForClientAsync(new[] { MSGraphScope });
return authResult;
}
Found Workaround Solution: getting a Token via REST API. Here I can get an User token or an Client token to access to graph api:
var client = new RestClient("https://login.microsoftonline.com/" + domainname);
var request = new RestRequest("/oauth2/token", Method.POST); request.AddBody("grant_type", "client_credentials");
request.AddParameter("client_id", clientId);
request.AddParameter("client_secret", clientSecret);
request.AddParameter("Resource", "https://graph.microsoft.com");
request.AddParameter("scope", "[scopes]");
IRestResponse response = client.Execute(request);
//contains the token
var content = response.Content;
According to your description, I assume you need a solution to authenticate a user without any interaction.
We can get an Access Token by some background services or daemons.
For more details, we can refer to this document.
Base on my test, we can try the following steps:
First, we should get administrator consent:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = "openid profile",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, NameClaimType = "name" },
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = this.OnAuthenticationFailedAsync,
SecurityTokenValidated = this.OnSecurityTokenValidatedAsync
}
});
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(Startup.clientId, string.Format(AuthorityFormat, tenantId), Startup.redirectUri,
new ClientCredential(Startup.clientSecret), null, appTokenCache.GetMsalCacheInstance());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new[] { MSGraphScope });
Then, we can use this access token to using the Graph APIs.
For more details, we can review to v2.0 daemon sample on GitHub.

Categories