Series overview
- Handle the incoming notification with an Azure Function (first post)
- Verify the invoice state within the Azure Function, but store the API credentials in the most secure way (this post)
- Send a push notification via Azure Notificationhub to all devices that have linked a merchant’s account to it (third post [coming soon])
Storing credentials securely
When we consume web services, we need to authenticate our applications against them/their API. In the past, I always saw (and in fact, I also made) samples that do ignore the security of those credentials and provide them in code, leaving additional research (if you are new to that topic) and making the samples incomplete. As the goal of this post is to verify the state of a received invoice against the AtomicPay API, the first step is to provide a secure mechanism for storing our API credentials on Azure.
Get your AtomicPay API keys
If you haven’t signed up for an AtomicPay account, you should probably do it now. Once you have logged in, open the menu and select ‘API Integration‘:

On this page, you find your pre-generated API-Keys. Should your keys be compromised, you will be able to regenerate your keys here as well:

Introducing Azure KeyVault
According to the Azure KeyVault documentation, it is
a tool for securely storing and accessing secrets. A secret is anything that you want to tightly control access to, such as API keys, passwords, or certificates. A Vault is
logical group of secrets.
As we are dealing with a public/private key credential combo, Azure KeyVault fits our goal to securely store the credentials perfectly. Since November last year, Azure Functions are able to use the KeyVault via their AppSettings. This is what the first half of this post is about.
Creating a new KeyVault
Login to the Azure Portal and go to the Marketplace. Search for ‘Key Vault‘ there and select it from the results:

You will be prompted with the

It will take a minute or two until the deployment is done. From the notification, you’ll get a direct link to the resource:

In the overview menu, select ‘Secrets’, where we will store our credentials:

If you are wondering why we are using the ‘Secrets‘ option over ‘Keys‘ – the keys are already generated by AtomicPay, and the import function wants a backup file. I did not have a deeper look into this option as the ‘Secrets‘ option offers all I need.
The next step is to add a secret entry for AtomicPay’s account id, public and private key:

Once you have added the Application ID and your keys into ‘Secrets‘, click on ‘Overview‘. On the right-hand side, you’ll see an entry called ‘DNS Name’. Hover with the mouse to reveal the copy button and copy it:

Now go to your Azure Function and select ‘Application settings‘. Scroll a bit down and add a new setting. You can name it whatever you want, I just used ‘KeyVaultUri‘. Paste the

Now we are just one step away of being able to use the KeyVault in our code. Go back to ‘Overview‘ on your Function and click on the ‘Platform features‘ tab. Select ‘Identity‘ there:

We need to enable the ‘System assigned‘ managed identity feature to allow the Function to interact with the Azure KeyVault from our code. This will add our Function to an Azure AD instance that handles the access rights for us:

Now we have everything in place to use the KeyVault to retrieve the API credentials we need for invoice verification.
Retrieving credentials in our code
Before we start to rewrite our function code, we need to install two NuGet-Packages into our Function project. Right click on the project and select ‘Manage NuGet Packages…‘. Search for these to packages and install them:
Refactoring the initial code
Whenever possible, we should take the chance to refactor our code. I have done so on adding the authentication layer for this sample. First, we will be moving the code to get the payload off the webhook into its own method:
private static async Task<WebhookInvoiceInfo> GetPaymentPayload(HttpRequestMessage requestMessage) { _traceWriter.Info("trying to get payload object from request..."); WebhookInvoiceInfo result = null; _jsonSerializerSettings = new JsonSerializerSettings() { MetadataPropertyHandling = MetadataPropertyHandling.Ignore, DateParseHandling = DateParseHandling.None, Converters ={ new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }, StringToLongConverter.Instance, StringToDecimalConverter.Instance, StringToInvoiceStatusConverter.Instance, StringToInvoiceStatusExceptionConverter.Instance } }; _jsonSerializer = JsonSerializer.Create(_jsonSerializerSettings); using (var stream = await requestMessage.Content.ReadAsStreamAsync()) { using (var reader = new StreamReader(stream)) { using (var jsonReader = new JsonTextReader(reader)) { result = _jsonSerializer.Deserialize<WebhookInvoiceInfo>(jsonReader); } } } return result; }
Next, we need are going to write another method to retrieve the credentials from the Azure KeyVault and initialize the AtomicPay SDK properly:
private static async Task<bool> InitAtomicPay() { bool isAuthenticated = false; //initialize Azure Service Token Provider and authenticate this function against the KeyVault var serviceTokenProvider = new AzureServiceTokenProvider(); var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(serviceTokenProvider.KeyVaultTokenCallback)); //getting the key vault url var vaultBaseUrl = ConfigurationManager.AppSettings["KeyVaultUri"]; //retrieving the appId and secrets var accId = await keyVaultClient.GetSecretAsync(vaultBaseUrl, "atomicpay-accId"); var accPBK = await keyVaultClient.GetSecretAsync(vaultBaseUrl, "atomicpay-accPBK"); var accPVK = await keyVaultClient.GetSecretAsync(vaultBaseUrl, "atomicpay-accPVK"); //initialize AtomicPay SDK and return the result await AtomicPay.Config.Current.Init(accId.Value, accPBK.Value, accPVK.Value); if (!AtomicPay.Config.Current.IsInitialized) { isAuthenticated = false; _traceWriter.Info("failed to authenticate with AtomicPay"); } else { isAuthenticated = true; _traceWriter.Info("successfully authenticated with AtomicPay."); } return isAuthenticated; }
Now that we authenticated against the API, we are finally able to verify the state of the invoice sent by the webhook. Let’s put everything together:
//renamed this function (not particullary necessary) [FunctionName("InvoiceVerifier")] public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log) { //enable other methods to write to log _traceWriter = log; _traceWriter.Info($"arrived at function trigger for 'InvoiceVerifier'..."); //retrieving the payload var payload = await GetPaymentPayload(req); //if payload is null, there is something wrong if (payload != null) { //authenticate the Function against AtomicPay var isAuthenticated = await InitAtomicPay(); if (isAuthenticated) { AtomicPay.Entity.InvoiceInfoDetails invoiceInfoDetails = null; _traceWriter.Info($"trying to verify invoice id {payload.InvoiceId} ..."); //verifying the invoice using (var atomicPayClient = new AtomicPay.AtomicPayClient()) { var invoiceObj = await atomicPayClient.GetInvoiceByIdAsync(payload?.InvoiceId); if (invoiceObj == null) return req.CreateResponse(HttpStatusCode.BadRequest, $"there was an error getting invoice with id {payload?.InvoiceId}. Response was null"); if (invoiceObj.IsError) return req.CreateResponse(HttpStatusCode.BadRequest, $"there was an error getting invoice with id {payload?.InvoiceId}. Message from AtomicPay: {invoiceObj.Value.Message}"); else invoiceInfoDetails = invoiceObj.Value?.Result?.FirstOrDefault(); } //this will be the point where we will trigger the push notification in the last part switch (invoiceInfoDetails.Status) { case AtomicPay.Entity.InvoiceStatus.Paid: case AtomicPay.Entity.InvoiceStatus.PaidAfterExpiry: case AtomicPay.Entity.InvoiceStatus.Overpaid: case AtomicPay.Entity.InvoiceStatus.Complete: log.Info($"invoice with id {invoiceInfoDetails.InvoiceId} is paid"); break; default: log.Info($"invoice with id {invoiceInfoDetails.InvoiceId} is not yet paid"); break; } } else { req.CreateResponse(HttpStatusCode.Unauthorized, "failed to authenticate with AtomicPay"); } } else req.CreateResponse(HttpStatusCode.BadRequest, "there was an error getting the payload from request body"); return req.CreateResponse(HttpStatusCode.OK, $"verified received invoice with id: '{payload?.InvoiceId}'"); }
Save the changes to your code. After that, right click on the project and hit ‘Publish’ to update the function on Azure. Once you have published the updated code, you should be able to test your Function once again with Postman and receive a result like this:

Conclusion
As you can see, Microsoft Azure provides a convenient way to store credentials and use them in your Azure Functions. This approach makes the whole process a whole lot more secure. This post also showed the ease of implementation of the AtomicPay .NET SDK.
In the third and last post of this series, we will connect our function to an Azure Notificationhub for sending push notifications to all devices that have registered with a certain merchant account. As always, I hope also this post was helpful for some of you.