- Blog
#Azure
How to get started with Azure Platform? We got you covered!
- 08/10/2024
Reading time 2 minutes
A well known fact: Passwords are an outdated form of authentication between APIs. The usual utilization of passwords as the primary access method exposes applications to leaks and interceptions by malicious actors. Even something as simple as brute-forcing a password is sometimes a legitimate threat.
Passwords come with all kinds of names: Pass, API key, user secret and others. So even the terminology might fool you to use something that is not so secure. As a side note, the word “token” is used with a lot of respect in the IT world today, mostly because it’s associated with modern authentication protocols like OAuth. But if a token never expires, is copied over and shared, you shouldn’t be trusting it. It’s just a password in disguise.
To better secure our applications we would like secrets that are more secure than just a string of characters. That is why certificates were developed. Certificates offer a range of perks compared to passwords, from implementing public key infrastructure to improved scoping. While dealing with Azure, probably the best option would be to use an Azure AD managed identity for all our authentication needs. But currently that is even more difficult to implement, and covering all the details of all the options is outside the scope of this post. The point here is that certificates are a good improvement over passwords, although not perfect.
Right away, I have to admit that using a certificate through OAuth2 in Azure AD is almost the same thing as using a client secret through OAuth2 in Azure AD. In both cases the secret or certificate exists in the app registration and Key Vault of our application, and in both cases a token is retrieved and validated.
The main reason I’m writing this post is to raise some awareness about the dangers of simple passwords, but also to provide a guide. Some clients might mandate the use of certificates which is a noticeably more difficult way to implement this kind of authentication than the client secret way. Especially when dealing with large organizations, you might not have a way out of the use of certificates. It may be a company policy that is set in stone.
Let’s take a look at a simple integration that implements authentication with a basic function key between two Azure Functions:
Once we want to upgrade security, let’s bring Key Vault and Azure AD into the mix:
Implementing OAuth2 obviously makes the setup more complex. Let’s look at the steps needed to accomplish this pattern.
I’m assuming some knowledge from the reader about Azure AD here, because otherwise this post would grow to be huge. I will try to explain the main points.
As if all this isn’t confusing enough, the related code has some associated mysteries as well. Let’s start with initializing our confidential client. What this means is creating an Azure AD client with the certificate from Key Vault that we are going to use to get our actual access token.
This could be our top level method:
private async Task InitializeClient()
{
var app = await _azureAdClientProvider.BuildConfidentialClientAsync();
var token = await GetAccessTokenAsync(app);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
And this is the client initialization:
public async Task<IConfidentialClientApplication> BuildConfidentialClientAsync()
{
var keyVaultUri = new Uri(_settings.KeyVaultBaseUrl);
var secretClient = new SecretClient(keyVaultUri, new DefaultAzureCredential());
var secretResponse = await secretClient.GetSecretAsync(_settings.KeyVaultCertificateName);
var secret = secretResponse.Value;
var privateKeyBytes = Convert.FromBase64String(secret.Value);
var certificate = new X509Certificate2(privateKeyBytes);
return ConfidentialClientApplicationBuilder.Create(_settings.ClientId)
.WithCertificate(certificate)
.WithAuthority(AzureCloudInstance.AzurePublic, Guid.Parse(_settings.TenantId))
.Build();
}
Short explanation of what is going on. We create a secret client to fetch the certificate private key from Key Vault. It seems that the way Key Vault works behind the scenes is that it does save some information to the secret storage about the certificate even though you have uploaded the certificate just to the certificate store. To fetch the private key part of the certificate, we need the secret client. In my opinion, it’s a little bit cryptic since there is also a certificate client available for you to create that can just fetch the public key, but let’s move on.
Then we just build the certificate from the private key. Note the Base64 conversion before creating the certificate. Also I feel like the settings that are described here are fairly self explanatory if you have worked with Azure AD before. Maybe one that I should mention is “KeyVaultCertificateName”. It’s just the exact display name you uploaded the certificate to Key Vault with.
If you look at the top level method (back up above), the next part is getting the token and then just attaching it to the “Authorization” header of our httpClient which may or may not be dependency injected.
private async Task<string> GetAccessTokenAsync(IConfidentialClientApplication app)
{
var scopes = new string[] { _scopeId };
var result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result.AccessToken;
}
ScopeId is of course the scope which you have been granted access in the receiving end. After attaching the token to the httpClient you can now make normal, authorized requests to the other API.
The receiving end of your queries (in our case Product API) also has to do some tricks to validate that the requesting API is actually legitimate. So that API will have to do something like this in the beginning of their Azure function:
var claimsPrincipal = await _azureAdJwtBearerValidation.ValidateTokenAsync(req.Headers.Authorization, requiredAppRole: AuthenticationConstants.AppRole);
if (claimsPrincipal == null)
{
log.LogInformation("Was not able to validate token");
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
The method implementation looks like this:
public async Task<ClaimsPrincipal> ValidateTokenAsync(string authorizationHeader, string requiredScope = null, string requiredAppRole = null)
{
if (string.IsNullOrEmpty(authorizationHeader))
{
return null;
}
if (!authorizationHeader.Contains("Bearer"))
{
return null;
}
var accessToken = authorizationHeader.Substring("Bearer ".Length);
var oidcWellknownEndpoints = await GetOIDCWellknownConfiguration();
var tokenValidator = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
RequireSignedTokens = true,
ValidAudience = _audience,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKeys = oidcWellknownEndpoints.SigningKeys,
ValidIssuer = oidcWellknownEndpoints.Issuer
};
try
{
_claimsPrincipal = tokenValidator.ValidateToken(accessToken, validationParameters, out var securityToken);
if (requiredScope != null && HasScope(requiredScope))
{
return _claimsPrincipal;
}
if (requiredAppRole != null && HasAppRole(requiredAppRole))
{
return _claimsPrincipal;
}
return null;
}
catch (Exception ex)
{
_log.LogError(ex.ToString());
}
return null;
}
Here we can check for the needed scope or application role. It can be one or the other exclusively. One thing to keep in mind is that if you are migrating from a simple function key to this model like in our example, it might be a good idea to keep the key active for a deployment or two before handing the authentication entirely to your new certificate based infrastructure, just to make sure everything works as intended.
If doing this, it also might be a good idea to remind the people on the other end not to remove the function key from their requests immediately after migrating. They did remove it in this case in my recent project, which broke the integration for a while during our testing session.
This post might make it seem that using this kind of authentication in your architecture is straightforward. But I remember a year ago implementing this, I was pretty lost. There’s just a lot of different concepts to handle at once. But I think it’s worth it to learn the dynamics of the pattern and the process, since working with Azure AD you will definitely face it again in some form at some point.
It’s interesting how security trends have evolved in the IT space over the years. My career is not that long, but even I remember a time when selling security to corporations was less like an opportunity and more like pulling teeth. Maybe this is just my experience, but thinking back it felt like customers always just wanted the next feature. Security was an afterthought.
Nowadays it seems like there’s a lot of security training/policies/mandates/audits baked into these organizations, and not just at the developer level. With the rise of things like ransomware this is a very positive and understandable course of action, although it sometimes may be pretty annoying to the more experienced developer or consultant. I think I have 3 mandatory corporate security certificates from big companies, all consisting of the same topics.
I guess the point of this post is less about using certificates and more about using some form of OAuth2 instead of simple passwords. Using client secrets in this way is almost as good as certificates. But the requirements and the restrictions of the client’s organization can take preference over pure developer logic. If that happens, as it did to us, I hope your life is a bit happier for having some sort of guide like this to lead you through the implementation process.
Our newsletters contain stuff our crew is interested in: the articles we read, Azure news, Zure job opportunities, and so forth.
Please let us know what kind of content you are most interested about. Thank you!