I have been battling with using Azure Key Vault in both development and production versions of my app for several days now. I can't seem to set things up correctly to gain access to my key vault from my app running locally during debug in VS 2017 or when deployed as a Web App on Azure. When I use the CLI with my account and resource group set I have no problem accessing a secret in my vault. However, when I try to access the same secret using the code below in my app I get an error stating that the access token was not obtained (paraphrased).
Imports System.Threading.Tasks
Imports Microsoft.Azure.KeyVault
Imports Microsoft.Azure.KeyVault.Models
Imports Microsoft.Azure.Services.AppAuthentication
Imports Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider
Public Class SocialXXXXXXX
Inherits System.Web.UI.Page
Public Property Message As String
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Message = "Your application description page."
Dim retries As Integer = 0
Dim retry As Boolean = False
Try
Dim azureServiceTokenProvider As AzureServiceTokenProvider = New AzureServiceTokenProvider()
Dim keyVaultClient As KeyVaultClient = New KeyVaultClient(New KeyVaultClient.AuthenticationCallback(AddressOf GetAccessTokenAsync))
Dim secret = keyVaultClient.GetSecretAsync("https://XXXXXXXvault.vault.azure.net/secrets/ExamplePassword/52ec77ddc2dXXXXXXX6c63f6c9").Result
TextBox7.Text = secret.Value
Catch keyVaultException As KeyVaultErrorException
TextBox7.Text = keyVaultException.Message
End Try
End Sub
Private Shared Function getWaitTime(ByVal retryCount As Integer) As Long
Dim waitTime As Long = (CLng(Math.Pow(2, retryCount)) * 100L)
Return waitTime
End Function
Public Async Function GetAccessTokenAsync() As Task(Of String)
Dim azureServiceTokenProvider = New AzureServiceTokenProvider()
Dim accessToken As String = Await azureServiceTokenProvider.GetAccessTokenAsync("https://XXXXXXXvault.vault.azure.net/")
Return accessToken
End Function
I would like to be able to access my secrets during development and production.
Update:
Another user over at https://forums.asp.net/p/2158070/6271824.aspx?p=True&t=636995498339724660 suggested a similar approach as #Joyey Cai below. However, I am reluctant to go that route as instead of using MSI, it requires me to include my app secret either in my code or in a setting. I have gone through your many tutorials and even deleted everything other than my web app from Azure and started over following MS's tutorial at https://learn.microsoft.com/en-us/learn/modules/manage-secrets-with-azure-key-vault/1-introduction and followed microsoft's recommendations verbatim (twice actually) and still have had not luck. I have also found that others are having the same issue and thus far I have not found that anyone has solved it. The problem appears to reside in the fact that Microsoft.Azure.Services.AppAuthentication is not authenticating the account which the developer is using for Azure Service Authentication (Tools>Azure>Azure Service Authentication in VS). I am also unsure if something else may be wrong with Microsoft.Azure.Services.AppAuthentication since not only will my app not get the secret when debugging locally but it also does not get the secret when deployed on the same machine that the key vault resides on. This is a very perplexing problem.
You could use the code as below and add permission to your application.
Imports Microsoft.Azure.KeyVault
Imports Microsoft.IdentityModel.Clients.ActiveDirectory
Public Class Class1
Shared appId As String = "xxxxxxxxxxxxxxx"
Shared appSecret As String = "xxxxxxxxxxxxxxx"
Shared tenantId As String = "xxxxxxxxxxxxxxx"
Public Shared Sub Main()
Dim kv = New KeyVaultClient(AddressOf GetAccessToken)
Dim scret = kv.GetSecretAsync("https://yourkeyvaultname.vault.azure.net", "yoursecretname").GetAwaiter().GetResult().Value
End Sub
Public Shared Async Function GetAccessToken(ByVal azureTenantId As String, ByVal clientId As String, ByVal redirectUri As String) As Task(Of String)
Dim context = New AuthenticationContext("https://login.windows.net/" & tenantId)
Dim credential = New ClientCredential(appId, appSecret)
Dim tokenResult = Await context.AcquireTokenAsync("https://vault.azure.net", credential)
Return tokenResult.AccessToken
End Function
End Class
Also, you need to add permission with "Key Vault" to the registered app.
In Key vault channel, you need to Add policies to your registered application or user. And in Access Control you need to add permission to your registered application or user.
The result:
Here is the C# code sample you can refer to.
Related
I've successfully implemented MSAL JS for Azure AD B2C.
The next step is to let the user edit their profile. I've created a new policy for Edit Profile.
But how to redirect the user there? There are only login methods / acquire token methods.
I've tried to set the authority to a different policy. It then does redirect to the right page, but then it starts complaining about errors in scopes, and it messes up the token locally.
editProfile() {
this.userAgentApp.authority = this.policyEditProfile;
this.userAgentApp.loginRedirect();
}
The ASP.NET code examples explicitly have an option to set the editProfile Policy ID: https://learn.microsoft.com/en-gb/azure/active-directory-b2c/active-directory-b2c-devquickstarts-web-dotnet-susi#update-code-to-use-your-tenant-and-policies
Feels like this is missing from MSAL.JS and I have to manually craft the URL, is that correct?
Yes, this is correct. You will need to use a different authority which URL is composed of the tenant and the policy name, as shown here:
private static string Tenant = "yourTenant.onmicrosoft.com";
public static string PolicySignUpSignIn = "b2c_1_susi";
public static string PolicyEditProfile = "b2c_1_edit_profile";
private static string BaseAuthority = "https://login.microsoftonline.com/tfp/{tenant}/{policy}/oauth2/v2.0/authorize";
public static string Authority = BaseAuthority.Replace("{tenant}", Tenant).Replace("{policy}", PolicySignUpSignIn);
public static string AuthorityEditProfile = BaseAuthority.Replace("{tenant}", Tenant).Replace("{policy}", PolicyEditProfile);
BTW, that sample, although for .NET Desktop shows how to use the edit profile and password reset policies: active-directory-b2c-dotnet-desktop , see in particular the EditProfileButton_Click method, the factor of acquiring the token (interactively) will trigger the dialog to edit the profile:
AuthenticationResult authResult = await App.PublicClientApp.AcquireTokenAsync(App.ApiScopes, GetUserByPolicy(App.PublicClientApp.Users, App.PolicyEditProfile), UIBehavior.SelectAccount, string.Empty, null, App.AuthorityEditProfile);
I have found how to upload/manage Azure Batch job Application Packages through the UI:
https://learn.microsoft.com/en-us/azure/batch/batch-application-packages
And how to upload and manage Resource Packages programmatically:
https://github.com/Azure/azure-batch-samples/tree/master/CSharp/GettingStarted/02_PoolsAndResourceFiles
But I can't quite seem to put 2 and 2 together on how to manage Application Packages programmatically. Is there an API endpoint we can call to upload/manage an Application Package when setting up a batch job?
Since this is not quite straightforward, I'll write down my findings.
These are the steps to programmatically upload Application Packages via an application that is unattended - no user input (e.g. Azure credentials) is needed.
In Azure Portal:
Create the Azure Batch application
Create a new Azure AD application (as Application Type use Web app / API)
Follow these steps to create the secret key and assign the role to the Azure Batch account
Note down the following credentials/ids:
Azure AD application id
Azure AD application secret key
Azure AD tenant id
Subscription id
Batch account name
Batch account resource group name
In your code:
Install NuGet packages Microsoft.Azure.Management.Batch, WindowsAzure.Storage and Microsoft.IdentityModel.Clients.ActiveDirectory
Get the access token and create the BatchManagementClient
Call the ApplicationPackageOperationsExtensions.CreateAsync method, which should return an ApplicationPackage
ApplicationPackage contains the StorageUrl which can now be used to upload the Application Package via the storage API
After you have uploaded the ApplicationPackage you have to activate it via ApplicationPackageOperationsExtensions.ActivateAsync
Put together the whole code looks something like this:
private const string ResourceUri = "https://management.core.windows.net/";
private const string AuthUri = "https://login.microsoftonline.com/" + "{TenantId}";
private const string ApplicationId = "{ApplicationId}";
private const string ApplicationSecretKey = "{ApplicationSecretKey}";
private const string SubscriptionId = "{SubscriptionId}";
private const string ResourceGroupName = "{ResourceGroupName}";
private const string BatchAccountName = "{BatchAccountName}";
private async Task UploadApplicationPackageAsync() {
// get the access token
var authContext = new AuthenticationContext(AuthUri);
var authResult = await authContext.AcquireTokenAsync(ResourceUri, new ClientCredential(ApplicationId, ApplicationSecretKey)).ConfigureAwait(false);
// create the BatchManagementClient and set the subscription id
var bmc = new BatchManagementClient(new TokenCredentials(authResult.AccessToken)) {
SubscriptionId = SubscriptionId
};
// create the application package
var createResult = await bmc.ApplicationPackage.CreateWithHttpMessagesAsync(ResourceGroupName, BatchAccountName, "MyPackage", "1.0").ConfigureAwait(false);
// upload the package to the blob storage
var cloudBlockBlob = new CloudBlockBlob(new Uri(createResult.Body.StorageUrl));
cloudBlockBlob.Properties.ContentType = "application/x-zip-compressed";
await cloudBlockBlob.UploadFromFileAsync("myZip.zip").ConfigureAwait(false);
// create the application package
var activateResult = await bmc.ApplicationPackage.ActivateWithHttpMessagesAsync(ResourceGroupName, BatchAccountName, "MyPackage", "1.0", "zip").ConfigureAwait(false);
}
Azure Batch Application Packages management operations occur on the management plane. The MSDN docs for this namespace are here:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.management.batch
The nuget package for Microsoft.Azure.Management.Batch is here:
https://www.nuget.org/packages/Microsoft.Azure.Management.Batch/
And the following sample shows management plane operations in C#, although it is for non-application package operations:
https://github.com/Azure/azure-batch-samples/tree/master/CSharp/AccountManagement
We are planning to implement Windows 10 UWP Application. We would like to authenticate user while accessing API Services hosted in Cloud.
Previously, we used the Microsoft.IdentityModel.Clients.ActiveDirectory NuGet package for authenticating in windows store 8.1. How do we authenticate user in Windows UWP Applications? I think AAD code for Windows Store and Windows Phone is different, how can we leverage AAD library for Windows 10 UWP applications. I have heard of Token Broker Authentication Architecture. Will this work for Azure Active Directory in addition to Facebook, etc.?
Please let me know if there is a workaround for AAD library to work in both Phone and Store (i.e. Universal App).
If you have an Native App that wants to access an API on Azure and authenticate with oAuth you need to use "OAuth 2.0 Authorization Code Flow" as describe on https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-protocols-oauth-code/.
This requires that you both your native app and api in the Azure Directory.
In https://azure.microsoft.com/nl-nl/documentation/articles/active-directory-devquickstarts-windowsstore/ a sample is given of a UWP App that accesses the graph.microsoft.com API, but you can replace this by your own API.
Sander,
If this answers your question please tag is such so we can help others.
Let me explain the steps.
You can still use Active Directory Authentication Library
in the UWP Apps.
To do it you have to add NuGet package (I pasted the link above). Once you do it there are few steps to implement authentication in your app:
1) Store information needed for the authentication (for instance in the App.xaml.cs constructor):
var localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values["ida:AADInstance"] = "https://login.windows.net/{0}";
localSettings.Values["ida:Tenant"] = "<<Name of your tenant here>>";
localSettings.Values["ida:ClientId"] = "<<Client ID Here>>";
localSettings.Values["ida:RedirectUri"] = "<<Redirect URI here>>";
localSettings.Values["ApiBaseAddress"] = "<<ID of Api Resource here>>";
localSettings.Values["ServiceAddress"] = "<<Address of your Api here>>";
Now write the code for authentication (this is helper class):
class ADContextHelper
{
ApplicationDataContainer _localSettings;
AuthenticationContext _authContext;
string _aadInstance;
string _tenant;
string _clientId;
Uri _redirectUri;
string _authority;
string _apiResourceId;
string _apiBaseAddress;
public ADContext()
{
_localSettings = ApplicationData.Current.LocalSettings;
configureSettings();
_authContext = new AuthenticationContext(_authority);
}
private void configureSettings()
{
_aadInstance = _localSettings.Values["ida:AADInstance"].ToString();
_tenant = _localSettings.Values["ida:Tenant"].ToString();
_clientId = _localSettings.Values["ida:ClientId"].ToString();
_redirectUri = new Uri(_localSettings.Values["ida:RedirectUri"].ToString());
_authority = String.Format(_aadInstance, _tenant);
_apiResourceId = _localSettings.Values["ApiResourceId"].ToString();
_apiBaseAddress = _localSettings.Values["ApiBaseAddress"].ToString();
}
public async Task<string> Authenticate()
{
AuthenticationResult authResult = await _authContext.AcquireTokenAsync(_apiResourceId, _clientId, _redirectUri);
//Here you retrieve the token:
var token = authResult.AccessToken;
return token;
}
}
At the end I also include code for logout - maybe you will want to include it:
public async Task<bool> Logout()
{
string requestUrl = "https://login.microsoftonline.com/" + _tenant + "/oauth2/logout?post_logout_redirect_uri=" + _redirectUri;
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
var response = await client.SendAsync(request);
}
I hope this will help you.
I'm trying to use SendGrid to send an email from an Azure worker role every time there are certain exceptions, but I can't get the email to send. I am using SendGridMail version 6.1.0.0 and SendGrid.SmtpApi version 1.3.1.0 which I installed via nuget and .Net 4.5. I am currently debugging locally with plans to deploy to Azure if i can get the emails to successfully send.
SendGridMessage myMessage = new SendGridMessage();
List<String> recipients = new List<String> { #"John Doe <johnd#outlook.com>", #"Peter Howe <perterhowe#gmail.com>" };
myMessage.AddTo(recipients);
myMessage.From = new MailAddress("myemail#test.com");
myMessage.Subject = "Error in Update";
myMessage.Text = "TESTING 123";
string username = XXXXXX;
string password = XXXXXXX;
// Create credentials, specifying your user name and password.
var credentials = new NetworkCredential(username, password);
// Create an Web transport for sending email.
var transportWeb = new Web(credentials);
// Send the email.
await transportWeb.DeliverAsync(myMessage);
As far as I can see I'm not getting any errors except when I debug and look at myMessage the Header has an error.
When I tried initializing a new empty header (var header = new Header();) I noticed there were still errors on that
To = 'header.To' threw an exception of type 'System.ArgumentException' Message = "Bad key path!"
Does anyone know what this means? Or if this could be causing the emails not to send?
The answer to your other question actually uses SendGrid:
Alerts for exceptions in an Azure worker role
There are three globalvariables:
public const string SmtpServerHost = "smtp.sendgrid.net";
public const string SmtpServerUserName = "[useridfromsendgrid#azure.com]";
public const string SmtpServerPassword = "[password from sendgrid]";
You actually do not need to use the SDK, just setup the account in Azure portal, and save your creds in your project.
You can send emails locally, but if you are on a work network, the firewall may block the emails from being sent. The code I posted I placed in an email service in my namespace.
It has be deployed to Azure to work. It won't work locally.
We're writing a system that allows a user to change their account password through a web application on our intranet.
At first, everything appeared to be running smoothly. During development passwords for our test accounts could be changed with no problem.
When we made the system live, however, we started running into issues. Here are the symptoms:
At first, everything is fine. Users
can change their passwords.
At some
point, the following error occurs in
UserPrincipal.FindByIdentity:
"System.Runtime.InteropServices.COMException:
The authentication mechanism is
unknown. "
From then on, trying to
change a password through the web
application results in the error:
"System.Runtime.InteropServices.COMException:
The server is not operational. "
If I manually recycle the app pool,
everything seems to fix itself until
more errors begin happening... i.e.,
the process starts all over again at
phase 1.
Here's the relevant snippet of code:
private static PrincipalContext CreateManagementContext() {
return new PrincipalContext(
ContextType.Domain,
ActiveDirectoryDomain,
ActiveDirectoryManagementAccountName,
ActiveDirectoryManagementAccountPassword);
}
private static void ChangeActiveDirectoryPasword(string username, string password) {
if (username == null) throw new ArgumentNullException("username");
if (password == null) throw new ArgumentNullException("password");
using (var context = CreateManagementContext())
using (var user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username)) {
user.SetPassword(password);
}
}
Any clues as to why this is happening? Google searches aren't turning up anything really helpful, and neither are the docs on MSDN.
First thing I notice is that you are using UserPrincipal.FindByIdentity which is inherited from AuthenticablePrincipal which is inherited from Principal. I say all this because the Principal class has a known memory leak in the FindByIdentity. If you have a look at this MSDN entry, you will notice at the bottom that Gary Caldwell from Microsoft said the following:
This call has an unmanaged memory leak
because the underlying implemenation
uses DirectorySearcher and
SearchResultsCollection but does not
call dispose on the
SearchResultsCollection as the
document describes.
I would guess that this is your issue. The memory leak causes the Application Pool to fill up and finally cause errors, until the Application Pool is reset and the memory is disposed.
When we use any active directory functions, we use the following to accomplish setting of the user's password:
Public Shared Function GetUserAccount(ByVal username As String) As DirectoryEntry
Dim rootPath As String = GetRootPath()
Using objRootEntry As New DirectoryEntry(rootPath)
Using objAdSearcher As New DirectorySearcher(objRootEntry)
objAdSearcher.Filter = "(&(objectClass=user)(samAccountName=" & username & "))"
Dim objResult As SearchResult = objAdSearcher.FindOne()
If objResult IsNot Nothing Then Return objResult.GetDirectoryEntry()
End Using
End Using
Return Nothing
End Function
Public Shared Sub SetPassword(ByVal username As String, ByVal newPassword As String)
Using objUser As DirectoryEntry = GetUserAccount(username)
If objUser Is Nothing Then Throw New UserNotFoundException(username)
Try
objUser.Invoke("SetPassword", newPassword)
objUser.CommitChanges()
Catch ex As Exception
Throw New Exception("Could not change password for " & username & ".", ex)
End Try
End Using
End Sub
Also, if you're wanting the users to change the passwords directly and you don't want to rely on their honesty, you might want to consider using the ChangePassword function of LDAP like this:
Public Shared Sub ChangePassword(ByVal username As String, ByVal oldPassword As String, ByVal newPassword As String)
Using objUser As DirectoryEntry = GetUserAccount(username)
If objUser Is Nothing Then Throw New UserNotFoundException(username)
Try
objUser.Invoke("ChangePassword", oldPassword, newPassword)
objUser.CommitChanges()
Catch ex As TargetInvocationException
Throw New Exception("Could not change password for " & username & ".", ex)
End Try
End Using
End Sub
This forces the user to know the prior password before changing to the new one.
I hope this helps,
Thanks!