I am learning the Azure Key Vault. I am trying to follow https://learn.microsoft.com/en-us/azure/key-vault/secrets/quick-create-net. I have created a vault in the portal, assigned an access policy for a user account. I have seen the vault in the protal, and I have seen that the user has I have set the environment variable value. I use an 'az login' command at a prompt to use that account. From the prompt I run the code in the quickstart. It prompts for a secret. I enter 'bob.' It throws an exception.
"Creating a secret in [...]-key-vault called 'mySecret' with the value 'bob' ...Unhandled exception. Azure.Identity.AuthenticationFailedException: SharedTokenCacheCredential authentication failed: A configuration issue is preventing authentication - check the error message from the server for details.You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details. Original exception: AADSTS70002: The client does not exist or is not enabled for consumers. If you are the application developer, configure a new application through the App Registrations in the Azure Portal at https://go.microsoft.com/fwlink/?linkid=2083908."
The exception is at
await client.SetSecretAsync(secretName, secretValue);
I think the problem is coming from
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
The client is not able to send tokens that the vault accepts. I am at a loss. I have had a couple of people with some expertise in using the Vault review this code and they haven't been able to provide insight. Any help?
Here is the code, from the example:
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
namespace key_vault_console_app
{
class Program
{
static async Task Main(string[] args)
{
const string secretName = "mySecret";
var keyVaultName = Environment.GetEnvironmentVariable("KEY_VAULT_NAME");
var kvUri = $"https://{keyVaultName}.vault.azure.net";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
Console.Write("Input the value of your secret > ");
var secretValue = Console.ReadLine();
Console.Write($"Creating a secret in {keyVaultName} called '{secretName}' with the value '{secretValue}' ...");
await client.SetSecretAsync(secretName, secretValue);
Console.WriteLine(" done.");
Console.WriteLine("Forgetting your secret.");
secretValue = string.Empty;
Console.WriteLine($"Your secret is '{secretValue}'.");
Console.WriteLine($"Retrieving your secret from {keyVaultName}.");
var secret = await client.GetSecretAsync(secretName);
Console.WriteLine($"Your secret is '{secret.Value}'.");
Console.Write($"Deleting your secret from {keyVaultName} ...");
DeleteSecretOperation operation = await client.StartDeleteSecretAsync(secretName);
// You only need to wait for completion if you want to purge or recover the secret.
await operation.WaitForCompletionAsync();
Console.WriteLine(" done.");
Console.Write($"Purging your secret from {keyVaultName} ...");
await client.PurgeDeletedSecretAsync(secretName);
Console.WriteLine(" done.");
}
}
}
Not Sure the root reason for it. But If you want to use a user account to login to Azure and access your key vault, using UsernamePasswordCredential() could be a workaround here.
To use UsernamePasswordCredential(), you should register a client application in Azure AD: Go to Azure portal =>Azure Active Directory => New registration
Note its application ID:
Go to API permissioms, and grant key vault user_impersonation permission so that users could access key vault via this app.
Click "Grant admin consent for.." to finish the permission grant process.
Go to the "Authentication" blade, turn on "Allow public client flows" so that Azure will consider this app as a public client:
Try the code below to create a secret:
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
namespace key_vault_console_app
{
class Program
{
static async Task Main(string[] args)
{
const string secretName = "mySecret2";
var keyVaultName = "<your kv name>";
var kvUri = $"https://{keyVaultName}.vault.azure.net";
var userCred = new UsernamePasswordCredential("<user account name>", "<user password>", "<your tenant name/id>", "<client application ID WHCIH we created above>");
var client = new SecretClient(new Uri(kvUri), userCred);
Console.Write("Input the value of your secret > ");
var secretValue = Console.ReadLine();
Console.Write($"Creating a secret in {keyVaultName} called '{secretName}' with the value '{secretValue}' ...");
await client.SetSecretAsync(secretName, secretValue);
Console.WriteLine(" done.");
}
}
}
Result:
Related
I have 2 Azure AD applications say Client-App and Server-App in Azure AD App registrations.
Server AD Application:
Registered a new App in Azure AD.
Set up App Roles with name "Search.Address" which is custom role.
Client AD Application:
Registered a new App in Azure AD.
API Permissions: Added the role "Search.Address" created in server-app registration is exposed as an Application Permissions in client app.
Granted Admin access successfully.
I have client Function App created with .NET stack and enabled system managed identity which is associated with Client-App. Client function app runs code to get an access token using ManagedIdentityCredential.
Token is successfully created but role "Search.Address" is missing.
I tried Client-App exposing as an API. But in no vain.
Does Managed identity have any permission to talk to server? How I can assign that using approleassignment ?
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.Empty;
try
{
var credential = new ManagedIdentityCredential();
var accessToken = await credential.GetTokenAsync(new TokenRequestContext(scopes: new string[] { "SERVERAPP_ClientID/.default" }) { });
responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. Your Token: {accessToken.Token}";
}
catch (Exception ex)
{
log.LogError(ex.Message+ex.InnerException.Message);
}
return new OkObjectResult(responseMessage);
}
}
Reference:
https://anoopt.medium.com/few-ways-of-obtaining-access-token-in-azure-application-to-application-authentication-40a9473a2dde
You need to assign the application permission/app role to the Managed Identity service principal.
You can't do this through the Portal at the moment, you'll need PowerShell to do that:
Connect-AzureAD
New-AzureADServiceAppRoleAssignment -ObjectId 1606ffaf-7293-4c5b-b971-41ae9122bcfb -Id 32028ccd-3212-4f39-3212-beabd6787d81 -PrincipalId 1606ffaf-7293-4c5b-b971-41ae9122bcfb -ResourceId c3ccaf5a-47d6-4f11-9925-45ec0d833dec
Note that this is for the older AzureAD module. There is also a similar cmdlet for the newer Microsoft.Graph module.
For the AzureAD cmdlet, the ids you need are:
ObjectId and PrincipalId: Managed Identity Service Principal object ID
Id: id of the app role/app permission
ResourceId: object ID of the API Service Principal
Running this command is the same thing as the admin consent for application permissions.
Article I wrote on this: https://joonasw.net/view/calling-your-apis-with-aad-msi-using-app-permissions
I am using the following code to query the Graph API to retrieve a Team's call information. However, I constantly get the error (pasted below). I am not sure what I am missing. Any help is much appreciated.
Code:
// The client credentials flow requires that you request the
// /.default scope, and preconfigure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
string[] scopes = new[] { "https://graph.microsoft.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
//string tenantId = "common";
string tenantId = AzureDetails.TenantID;
// Values from app registration
string clientId = AzureDetails.ClientID;
string clientSecret = AzureDetails.ClientSecret;
// using Azure.Identity;
TokenCredentialOptions options = new()
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
ClientSecretCredential clientSecretCredential = new(tenantId, clientId, clientSecret, options);
GraphServiceClient graphClient = new(clientSecretCredential, scopes);
call = await graphClient.Communications.Calls[id].Request().GetAsync();
Exception:
Code: UnknownError
Inner error:
AdditionalData:
date: 2022-08-30T17:11:46
request-id: b00e1f14-82d7-459b-bbdc-ae13ae04cdb2
client-request-id: b00e1f14-82d7-459b-bbdc-ae13ae04cdb2
ClientRequestId: b00e1f14-82d7-459b-bbdc-ae13ae04cdb2
Looks like the permission issue, These the are the permissions required for getting the communication call information:
Only application permissions are supported not delegated permissions for this API.
Please also check your token here https://jwt.ms/, if it has requested permissions.
I have created a MVC application to escalate work to other person inside my organization. I have added all the members in my organization to AAD,
and registered an application there, created app service and linked that app service to registered app with SSO enabled.
Now every time someone visits the app, they can login successfully using their respective credential.
What I want to do know is to retrieve all the members in my AAD and display them inside dropdown list so that anyone can escalate to others by just looking in the dropdown list.
I have tried with sample graph SDK to get the name of users in my organization
with this code
private string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private string appId = ConfigurationManager.AppSettings["ida:AppId"];
private string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private string scopes = ConfigurationManager.AppSettings["ida:GraphScopes"];
public async Task<string> GetUserAccessTokenAsync()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
TokenCache userTokenCache = new SessionTokenCache(signedInUserID, httpContext).GetMsalCacheInstance();
//var cachedItems = tokenCache.ReadItems(appId); // see what's in the cache
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
userTokenCache,
null);
try
{
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes.Split(new char[] { ' ' }), cca.Users.First());
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
throw new ServiceException(
new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = Resource.Error_AuthChallengeNeeded,
});
}
}
with some change in scope.
<add key="ida:AppId" value="xxxxx-xxxxxxx-xxxxxx-xxxxxxx"/>
<add key="ida:AppSecret" value="xxxxxxxxxxx"/>
<add key="ida:RedirectUri" value="http://localhost:55065/"/>
<add key="ida:GraphScopes" value="User.ReadBasic.All User.Read Mail.Send Files.ReadWrite"/>
This enables me to get basic details of all user in my organization.
But how I can achieve this in my app where authentication related stuffs are done in azure only, and there is no code for authentication and authorization in entire solution.
Thanks
Subham, NATHCORP, INDIA
But how I can achieve this in my app where authentication related stuffs are done in azure only, and there is no code for authentication and authorization in entire solution.
Based on my understanding, you are using the build-in feature App Service Authentication / Authorization. You could follow here to configure your web app to use AAD login. And you need to configure the required permissions for your AD app as follows:
Note: For Azure AD graph, you need to set the relevant permissions for the Windows Azure Active Directory API. For Microsoft Graph, you need to configure the Microsoft Graph API.
Then, you need to configure additional settings for your web app. You could access https://resources.azure.com/, choose your web app and update App Service Auth Configuration as follows:
Note: For using Microsoft Graph API, you need to set the resource to https://graph.microsoft.com. Details, you could follow here.
For retrieving the access token in your application, you could get it from the request header X-MS-TOKEN-AAD-ACCESS-TOKEN. Details, you could follow Working with user identities in your application.
Moreover, you could use Microsoft.Azure.ActiveDirectory.GraphClient package for Microsoft Azure Active Directory Graph API, Microsoft.Graph package for Microsoft Graph API using the related access token.
I've created a multi tenant Web API that works just fine. Now I want to build a native client for testing. The Web API app is defined in one tenant (webapitenant). The test app is defined in another tenant (clienttenant) that has given admin consent to the Web API.
I've added the testClientId as a knownClientApplication in the Web API's app manifest and oauth2AllowImplicitFlow enabled. The test client has been granted permissions to the Web API app.
GetAccessToken:
var userCredential = new UserCredential("admin#clienttenant.onmicrosoft.com", "password");
var context = new AuthenticationContext("https://login.windows.net/common");
return context.AcquireToken("https://webapitenant.onmicrosoft.com/webApiResourceUri", testClientId, userCredential).AccessToken;
Exception thrown: 'Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException' in Microsoft.IdentityModel.Clients.ActiveDirectory.dll
Additional information: AADSTS65001: The user or administrator has not consented to use the application with ID 'nativeclientid'. Send an interactive authorization request for this user and resource.
Exception thrown: 'Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException' in Microsoft.IdentityModel.Clients.ActiveDirectory.dll
Additional information: AADSTS65001: The user or administrator has not consented to use the application with ID nativeclientid. Send an interactive authorization request for this user and resource.
Update
I created a dummy console app to force a consent form that I could accept. ADAL now returns tokens but my Web API rejects them (status 401).
var parameters = new PlatformParameters(PromptBehavior.Always);
var context = new AuthenticationContext("https://login.windows.net/common");
var token = context.AcquireTokenAsync
("https://webapi.onmicrosoft.com/appuri",
"testappid",
new Uri("https://webapi.azurewebsites.net"), parameters).Result.AccessToken;
Console.WriteLine(token); //Output: oauth token
var client = new HttpClient
{
BaseAddress = new Uri("https://webapi.azurewebsites.net/api/")
};
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = client.GetAsync("tenants").Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
// Output: {"$type":"System.Web.Http.HttpError, System.Web.Http","Message":"Authorization has been denied for this request."}
Please ensure that the web app is ignore the issue validation and the audience is same as the resource(https://webapi.onmicrosoft.com/appuri",
"testappid) you acquire for the access token and this value should be the App ID URI which you can find it on old Azure portal like figure below:
Here is the relative code for setting for the authentication of multi-tenant web API:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters= new System.IdentityModel.Tokens.TokenValidationParameters {
ValidateIssuer=false
}
});
I am developing a piece of code which can create and download reports using Google DFA Reporting API's.
I am able to do the same using Client Id and Client Secret, which is generated using Install-App (native Application) account, but with this type of account, it always open a browser for first time and then only it authenticates the future request.
On further reading, I came across the service account. I then created a new Service Account and downloaded p12 key. I am now able to build credential object with the help of p12 and Email account (*******ccms#developer.gserviceaccount.com). I can confirm this as I am seeing access token in a credential object after calling credentials.refreshToken().
I then create an object of DFA Reporting using the above credential and trying to fetch profiles list, but I am getting the below error:
java.lang.IllegalArgumentException: No profiles found
at com.google.api.client.repackaged.com.google.common.base.Preconditions.checkArgument(Preconditions.java:92)
at com.google.api.client.util.Preconditions.checkArgument(Preconditions.java:49)
at com.google.api.services.samples.dfareporting.cmdline.GetAllUserProfiles.list(GetAllUserProfiles.java:52)
at com.google.api.services.samples.dfareporting.cmdline.DfaReportingSample.main(DfaReportingSample.java:171)
Please see my code below and let me know where I am going wrong:
private static Credential authorize() throws Exception {
// load client secrets
List<String> SCOPES = ImmutableList
.of("https://www.googleapis.com/auth/dfareporting");
String SERVICE_ACCOUNT_EMAIL = "*************************#developer.gserviceaccount.com";
String Path = DfaReportingSample.class.getResource(
"/TestDFA-5d985ff38b34.p12").getPath();
java.io.File file = new java.io.File(Path);
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
Credential credentials = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKeyFromP12File(file).build();
credentials.refreshToken();
return credentials;
}
private static Dfareporting initializeDfareporting() throws Exception {
Credential credential = authorize();
// Create DFA Reporting client.
return new Dfareporting(httpTransport, JSON_FACTORY, credential);
}
public static void main(String[] args) {
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
Dfareporting reporting = initializeDfareporting();
UserProfiles up = reporting.userProfiles();
List l = up.list();
UserProfileList profiles = l.execute();
// {"etag":"\"bM2H6qONz9kIDiByk_eTdC6Ehcc/vyGp6PvFo4RvsFtPoIWeCReyIC8\"","items":[],"kind":"dfareporting#userProfileList"}
Preconditions.checkArgument(
profiles.getItems() != null && !profiles.getItems().isEmpty(), "No profiles found");
for (UserProfile userProfile : profiles.getItems()) {
System.out.printf("User profile with ID \"%s\" and name \"%s\" was found.%n",
userProfile.getProfileId(), userProfile.getUserName());
}
....................................
PS: I am able to fetch all the profiles, if I use access token which is generated using client id and client secret provided by native application (installed apps) account.
Thanks,
Hussain Bohra
After trying a lot with service account (& p12 key), we couldn't make it work with the Google DFA Reporting. Hence we are now using the following class for authentication:
com.google.api.ads.common.lib.auth.OfflineCredentials
input = new FileInputStream(Utils.getProperty(Constants.HOME_PATH)
+ Utils.getProperty(Constants.CLIENT_SECRETS_DIRECTORY)
+ Constants.PATH_SEPARATOR + userPropertyFile);
userProperty.load(input);
credential = new OfflineCredentials.Builder()
.forApi(OfflineCredentials.Api.DFA)
.withHttpTransport(
GoogleNetHttpTransport.newTrustedTransport())
.withClientSecrets(userProperty.getProperty("client_id"),
userProperty.getProperty("client_secret"))
.withRefreshToken(userProperty.getProperty("refresh_token"))
.build().generateCredential();
The refresh token used in the above code is generated manually (one time process) for every account. Using this object of credential, we are now able to perform all the following activities in a DFA reporting:
- List Profiles
- Validate and Create Report
- Download Report Data.
Code to get refresh token (involves a manual step of clicking Accept in a browser)
java.io.File DATA_STORE_DIR = new java.io.File(
"d:/dfa-reporting/.store/dfareporting_sample");
FileDataStoreFactory dataStoreFactory;
dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR);
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(
JSON_FACTORY,
new InputStreamReader(DfaReportingSample.class
.getResourceAsStream("/client_secret_galtieri.json")));
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(dataStoreFactory).build();
// authorize
String refreshToken = new AuthorizationCodeInstalledApp(flow,
new PromptReceiver()).authorize("hbohra").getRefreshToken();
Thanks,
Hussain Bohra