How to Handle Credential Provisioning and Key Rolling with the Microsoft Graph API

Not too long ago I investigated the options to manage the lifecycle of Azure Active Directory app registrations at scale. Most importantly, it needed to be fully automated; the numbers are simply too large to have manual steps in the entire process. For obvious reasons, the Microsoft Graph API plays a big role in making this a reality. And while the documentation and samples are pretty comprehensive (especially for the more common use cases), I stumbled upon a little gem in the API that’s not documented at all, and only sparsely so in the documentation for the Azure AD Graph API (the predecessor of the Microsoft Graph). It’s the addKey (and removeKey) action on the Application object, and in the end it enabled me to do key rolling with nothing more than direct communication between the registered app and the Graph API. But it took me half a day to get it to work, so I’m sharing my findings here; maybe it saves someone that half a day. I’ll be going through the details of how this works; if you’re just looking for the end-to-end solution, just skip right over there.

Let’s start with an outline of what we’re trying to achieve. First of all, upon provisioning a new app, we want to provision it with a temporary key of some sort. This temp key should enable the app to generate and register its own key that has a more extended validity period. That way, we want to ensure that only the app itself and Azure AD have knowledge of keys that are valid for a prolonged period of time. And secondly, even though the app-generated key should be valid for a longer period, best practices dictate that it should still have an expiration date. Determining a reasonable validity period depends on the context and possible compliancy regulations, but something like 1 or 2 years would be typical. When the expiration date approaches, the app needs to generate a new key, register that with Azure AD, and possibly retract the previous key.

Now, Azure AD app registrations allow for both symmetric and asymmetric (i.e. certificate) keys, but it’s a best practice to use asymmetric keys wherever possible. To add to that, certificate credentials are required for the approach I’m detailing here, so we’re using certificates. All the heavy lifting is done by a request to https://graph.microsoft.com/v1.0/applications/{id}/microsoft.graph.addKey
– which, as said, is not documented anywhere in the documentation. It is mentioned in the Azure AD Graph API documentation, and it was only through the Microsoft.Graph Nuget package that I suspected it might be available and functional in the Microsoft Graph. So based on the Azure AD Graph docs, let’s dissect all the pieces for a valid request.

Authorization header

Of course, every call to the Graph API must include a Bearer token in the Authorization header. There are numerous examples online on how to obtain such a token; one way would be to use the Microsoft Authentication Library (MSAL):


private async Task<string> GetTokenAsync(string appId, X509Certificate2 certificate, string tenantId)
{
var app = ConfidentialClientApplicationBuilder.Create(appId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}/")
.WithCertificate(certificate)
.Build();
var scopes = new[] { "https://graph.microsoft.com/.default" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
}

The interesting bit here is that the token should represent the app for which we’re trying to call addKey. So we don’t need another app identity that has permissions to manage apps in the Azure AD tenant to make this call for us. In fact, that’s not even possible: this addKey action seems to be designed from the ground up to provide self-service key management functionality to registered apps. And the best thing is that the app doesn’t need any special permission for this; a newly registered app with default permissions can do this just fine.

Proof

Moving on to the request body, the proof property is the most interesting one: it’s supposed to be “A signed JWT token used as a proof of possession of the existing keys“. And this existing key “is the private key of one of the application existing certificates“. This is why certificate credentials are required for this approach. Together with some other requirements for this self-signed JWT token, the full code for constructing one looks like this:


private string GetJwtTokenProof(X509Certificate2 signingCert, string appId)
{
var notBefore = DateTime.Now;
var expires = notBefore.AddMinutes(10);
var handler = new JwtSecurityTokenHandler();
var credentials = new X509SigningCredentials(signingCert);
var jwtToken = handler.CreateJwtSecurityToken(appId, "https://graph.windows.net", null, notBefore, expires, null, credentials);
return handler.WriteToken(jwtToken);
}

Note that this code requires the System.IdentityModel.Tokens.Jwt Nuget package.

Key credential

The details regarding the request body depend on whether or not you’re using the Graph client (as opposed to manually constructing the HTTP calls, for example), but if you are, this is simply a matter a creating a KeyCredential object:


private KeyCredential CreateKeyCredential(X509Certificate2 certificate)
{
return new KeyCredential()
{
Key = certificate.RawData,
Usage = "Verify",
Type = "AsymmetricX509Cert"
};
}

Putting it all together

This is all there is to it to enable an app to make a call to Azure AD to register a new certificate for itself (or revoke one, for that matter). So it nicely fulfills our requirements: we can provision the app with a temporary certificate we create centrally, with a validity of just 1 or 2 days. Using that certificate, the app can self-sign a new certificate, use the temporary one to sign the JWT token proof to register the new one, and then use the newly registered certificate to revoke the temporary one. Equally, when a certificate is about to expire, it can use the same flow to create and register a new one and revoke the old one. The complete code looks like this:


using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace AzureADAppManagement
{
public class AppCertificateManager
{
public async Task RollCertificatesAsync(string appId, string appObjectId, string tenantId, X509Certificate2 existingCertificate, X509Certificate2 newCertificate)
{
var graph = GetGraphClient(() => GetTokenAsync(appId, existingCertificate, tenantId));
var keyCredential = CreateKeyCredential(newCertificate);
var proof = GetJwtTokenProof(existingCertificate, appId);
await graph.Applications[appObjectId].AddKey(keyCredential, proof).Request().PostAsync();
// Waiting 120 secs; proceeding immediately may result in failure if the new cert is not fully processed server side
// Not sure how much time is appropriate here, to be honest. 120 secs seems excessive, but 20 secs for example has proven too short
await Task.Delay(120000);
// Re-init the client to use the new cert for token retrieval
graph = GetGraphClient(() => GetTokenAsync(appId, newCertificate, tenantId));
// Find the keyId for the old certificate
var app = await graph.Applications[appObjectId].Request().GetAsync();
var keyId = app.KeyCredentials.Single(key =>
{
return Convert.ToBase64String(key.CustomKeyIdentifier).Equals(existingCertificate.Thumbprint, StringComparison.OrdinalIgnoreCase);
}).KeyId;
// Create new proof based on the new certificate
proof = GetJwtTokenProof(newCertificate, appId);
// Remove the old certificate
await graph.Applications[appObjectId].RemoveKey(keyId.Value, proof).Request().PostAsync();
}
private GraphServiceClient GetGraphClient(Func<Task<string>> tokenDelegate)
{
return new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
var token = await tokenDelegate();
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", token);
}));
}
private async Task<string> GetTokenAsync(string appId, X509Certificate2 certificate, string tenantId)
{
var app = ConfidentialClientApplicationBuilder.Create(appId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}/")
.WithCertificate(certificate)
.Build();
var scopes = new[] { "https://graph.microsoft.com/.default" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
}
private string GetJwtTokenProof(X509Certificate2 signingCert, string appId)
{
var notBefore = DateTime.Now;
var expires = notBefore.AddMinutes(10);
var handler = new JwtSecurityTokenHandler();
var credentials = new X509SigningCredentials(signingCert);
var jwtToken = handler.CreateJwtSecurityToken(appId, "https://graph.windows.net", null, notBefore, expires, null, credentials);
return handler.WriteToken(jwtToken);
}
private KeyCredential CreateKeyCredential(X509Certificate2 certificate)
{
return new KeyCredential()
{
Key = certificate.RawData,
Usage = "Verify",
Type = "AsymmetricX509Cert"
};
}
}
}

Why I prefer this approach

Of course there are different ways of handling credential provisioning and key rolling. For example: the application could just be provisioned with a centrally generated (symmetric or asymmetric) key that’s intended as the definitive key (during that 1 or 2 year validity period). However, this would mean that this central agent has, at some point in time, knowledge of these long-term credentials, which would increase the risk associated and therefore the measures taken to properly protect it. The same applies to key rolling: you could have the apps call into a custom-built API to signal its desire to renew its key, or you could orchestrate the key rolling process from a central agent altogether. But again, that would imply having these credentials available in a runtime that’s neither client nor Identity Provider. Furthermore, this agent would need extensive permissions on the Microsoft Graph to actually be able to register new credentials. Especially in case this agent is callable by external parties (such as a client initiating a key rolling process), you would need to make very sure that you’ve covered your bases to prevent Elevation of Privilege.

And just to reiterate: the addKey approach works without special Microsoft Graph permissions, and it only works when the call includes a Bearer token that represents the app itself, so the possible attack surface is greatly reduced. Of course you’d still need to centrally provision that initial temporary certificate, so security measures are still applicable for the agent handling that, but the keys it generates can have a very limited validity. And since it plays no part in the key rolling process, it’s not callable from the outside, and is therefore more easily secured.

So, all in all, I really like this hidden gem in the Microsoft Graph API. Let me know what you think in the comments!

Advertisement

Updates on IP Restrictions for Azure App Services

Two weeks ago, I wrote about the new VNet Integration feature on Azure App Services. This has everything to do with being able to lock down downstream systems to only accept traffic coming from a specific VNet under your control, as opposed to a set of public IP addresses that are managed by Microsoft, shared with other tenants, and prone to change.

Today it’s time to follow up on that. Because we may not only want to protect downstream systems, but also the web apps themselves. For public web apps, that protection typically does not exist at the network level, but wouldn’t it be nice if there was some network-level protection option available for private apps? Until now, the only real way to do that was by employing a very pricey App Service Environment. An ASE sits in your own VNet with all the security and flexibility this brings, but it is exceedingly expensive.

But the need to actually deploy an ASE has now disappeared if all you want to do is lock down access to a web app so that it’s only available from your network: the Microsoft.Web service endpoint has just become available!

That’s right: you can now configure a service endpoint for Azure App Services on one or more of your subnets:

ServiceEndpoint

Then, on the Web App, you can configure IP restrictions to only allow your subnet to access the Service Endpoint:

AccessRestriction

So, there you have it: not only can you rigorously protect access to downstream systems while allowing traffic originating from Azure App Services; you can now also protect Azure App Services itself to only allow internal traffic!

New: SAS token support in the new Azure Service Bus .NET Core client

For some time now, Azure Service Bus comes with two client libraries. The first is the good old WindowsAzure.ServiceBus, which is functionally complete and mature, but requires the full .NET Framework 4.5. The second is the new Microsoft.Azure.ServiceBus library which targets .NET Standard and is therefore usable within .NET Core, but is not functionally complete yet.

ASB

But a couple of days ago, at least one of those functional omissions was (partly) resolved with the release of version 2.0.0 of the client, because this version now offers rudimentary support for SAS tokens. Rudimentary because it will not generate tokens for you yet, but it will play nicely with tokens you crafted yourself.

Why is that important? Well, because SAS tokens play a key role in messaging scenarios that cross organizational boundaries. When two parties from different organizations communicate via Request/Reply messaging for example, one or both parties will be communicating via one or more queues that belong to the other party’s organization. In those situations, you’d typically prefer granting access using SAS tokens instead of keys.

Because crafting a SAS token is a rather precise task that can take some time for first-timers, I created a simple Request/Reply sample that involves using a SAS token. It’s intentionally kept simple so you will want to expand upon it before using it in your own application; it just aims to showcase the general idea of generating and using SAS tokens in a Request/Reply scenario. Let me know what you think in the comments!

HowTo: Secure a Custom Webhook for Azure Event Grid

As I wrote before, I’m playing around with the new Azure Event Grid lately. As I mentioned in my previous post, custom event publishers and subscribers hold a lot of promise, especially while we are still awaiting the bulk of Azure services to be hooked up to Event Grid.

AEG

But for custom publishers and subscribers to actually lift off, we need some way to authorize calls, both those from the publisher to Event Grid and those from Event Grid to the subscriber endpoint. Now the first is pretty well covered in the docs. But the call from Event Grid to the subscriber endpoint is not very well described at this point in time. It just mentions some initial validation sequence, which is supposed to prove ownership of the endpoint but in actuality just verifies that the endpoint is expecting to handle Event Grid events.

If this were the whole story, having an Event Grid subscriber endpoint would imply accepting unauthorized calls containing event payloads, meaning anyone with knowledge of the endpoint address can send bogus events your way – and since you have no way to tell authentic from fake events, you’d also be opened up for Denial of Service.

503-error

Luckily, a conversation with the Product Team quickly revealed that this is not the whole story. When you register a subscriber endpoint in Azure Event Grid, you can include a query string. This query string will be included in each and every call to your endpoint, so both the initial validation call and subsequent event notification calls. If you put some sort of key in there and then verify its presence in each incoming call, you’ve effectively locked out the Man In The Middle, and you just made a Denial of Service a lot harder.

EditEventSub

Furthermore, query strings that are added this way are not visible when enumerating Event Grid Subscribers in the portal, as an added layer of security.

ListEventSubs

I’ve updated my code samples to include a possible way to handle this for a ASP.NET Core WebAPI webhook.

Thanks to Dan Rosanova for clearing up how authorizing Event Grid calls to custom webhooks can be done.

What Exactly Is That CORS-Thing?? The What, the Why and the How Explained

If you’ve stumbled upon this post, chances are you’ve encountered some strange behavior while trying to call an endpoint, like a REST API for example, from within the browser. You may have seen your browser issue an OPTIONS request that is greeted with a 405 Method Not Allowed issued by the API.

fiddler

If this has happened to you, you are probably serving the JavaScript from another application than the one hosting the API.

You were probably expecting an XmlHttpRequest fetching a JSON document instead of that failed OPTIONS request, so read on to find out what’s going on and what you can do to successfully call that API.

So What’s CORS?

In short, you would probably benefit from enabling CORS on your API. CORS is short for Cross Origin Resource Sharing, and enabling CORS is basically a way of allowing your web application to call the API from the client browser, while that API is hosted on a host different than the one your web application is served from. You are not allowed to do that out of the box for security reasons. If you are only interested in actually getting this to work, feel free to skip to the How-part of this post.

OK, so you are interested in a little more background. As said, you can’t call API’s from the browser out of the box if they do not reside on the same host (‘have the same origin’) as the web application. Same origin here means: same URI scheme, hostname and port number. This behavior is enforced by the browser. If a piece of JavaScript attempts to call an API of different origin than its own, the browser will first makes a pre-flight request to the target server to ask whether the server is OK with being called from another origin. Enabling CORS means: instructing the API on how to meaningfully respond to such pre-flight requests. Without CORS enabled, API’s typically respond with the 405 we talked about. Most modern browsers support this pre-flight request, which is a prerequisite for using CORS.

Why CORS?

The ‘security reasons’ behind all this are known as the same-origin policy. According to this principle, resources are isolated from each other on the basis of their origin. So, a piece of script for example can only access other documents in the browser when they share the same origin, and it can only call endpoints on that same origin; all resources from other origins are off-limits.

cross-blocked

Source: http://www.lucadentella.it/en/2013/07/11/javascript-same-origin-policy-e-jsonp/

This makes good sense, because failure to restrict this would mean that a malicious web page that is opened in a user’s browser session would have access to all documents and endpoints for other websites the user is also visiting. Imagine one of these other websites being your personal banking environment, and you probably get why the same-origin policy is kind of a good thing.

But obviously, there are also legitimate use cases for cross-origin API calls. Strategists, visionaries and evangelists preach the API-driven world, in which every company should disclose their processes through API’s to be consumed by clients. Those clients typically will not reside on the same origin, but we do want them to be able to call our API’s.

In recent years, several hacks have been conjured up to bypass the same-origin policy, with JSONP being one of the more prominent ones. I won’t dive into the specifics here; you can read all about it online. The issue with JSONP (apart from some sophisticated exploits) is that, as an API publisher, you open up your API for all origins by definition. And this is where CORS comes in: a controlled way of whitelisting some origins while treating all others according to the same-origin policy. And, as a bonus, the implementation is much easier: CORS is entirely a server-side setting, whereas JSONP requires the client to do part of the heavy lifting.

HowTo CORS

So, on to the actual way of doing this. And this is actually the simplest part: you just need to make sure that the API responds differently to the OPTIONS request. What the browser is actually asking, by means of the Origin header it sends along, is whether the specified origin is allowed to call the API. The API may either not allow this at all (the default), only allow a specific list of origins, or allow all origins. And it communicates this by including a Access-Control-Allow-Origin header to the response to the pre-flight request.

fiddler2

A specific value is indicative of an API that allows this specific origin. Alternatively, an asterisk (*) indicates that all origins are allowed.

For a .NET-based WebAPI, you can use OWIN middleware or the WebAPI CORS package, depending on your application architecture and the requirements. The use of CORS through OWIN middleware is nicely described here, while the CORS package method is detailed over here.

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    }
}

And yes, you can only enable CORS on the API side; not on the caller side. After all, the same origin policy is meant to protect the API from access by malicious websites the user may be visiting.

Hope this helps!

Blast from the Past: A Delegation Scenario using WS-Federation and WS-Trust

A couple of weeks ago, I wanted to outline some of the different flavors and protocols available for delegation scenarios using a federated identity. One of the protocols on my list was WS-Federation and WS-Trust. Yes, I know, all the cool kids are doing OpenID Connect these days, but some of us are working for enterprises that bought into the whole federation-thing rather early and while still on-premise. For those environments ADFS is most likely the Identity Provider. And if the relying parties are .NET-based apps, the protocol of choice for identity federation is WS-Federation.

Of course, I did want to use the latest and greatest as much as possible, so I checked out the new OWIN/Katana gear for WS-Federation. And sure enough, getting identity federation to work using ADFS as the Identity Provider was a breeze. However, delegating the federated identity to a backend WCF service: not so much…

The theory here is that, firstly, the WCF service is also registered as a relying party in ADFS; secondly, that the web application is allowed to delegate identities to that relying party; and thirdly, that the web application can use the ADFS-issued user token to send back to ADFS as part of the request for a delegation token. Now the issue I encountered is that the token, as persisted by the OWIN middleware, does not have the same format as is expected by the time the delegation call is being made. More specifically, the token is persisted as a string, whereas the delegation code is expecting a SecurityToken.

I’ve tried to work this out in just about every way I could think of. This was not exactly made easier by the utter lack of online resources when it comes to WS-Federation (especially in its .NET 4.5 and OWIN incarnations). Still, I did not get this to work using the OWIN middleware. So I defaulted back to the ‘classic’ way of making this work, configuring the initial federation with ADFS through the web.config for both the front end MVC application and the backend WCF service that the web app is calling into. And as said, the online resources on WS-Federation in .NET 4.5 are limited, so I figured I’d share my sample on Github.

There’s a lot of moving parts to this sample, and principles to grasp if you want to fully understand the code. Luckily, all of that is pretty much covered in this guide. The ADFS part of it is pretty accurate as it is, and even though it is aimed at ADFS 2.0, it’s easily transferable to ADFS 3.0. As far as the code goes, the principles remain the same but the implementation is based on WIF on .NET 4.0. So you’ll have to do some digging through my sample to match it to the way it is described in the guide. Just see it as a learning opportunity ;).

I will reveal one difference: the guide assumes that the account running the web application is domain-joined so the web app can authenticate itself to ADFS using Windows Authentication when it makes the call to get the delegation token. To simplify the setup, I chose to authenticate to ADFS using a username and password so that I wouldn’t have to set up Kerberos. To make the username-based binding work, I used Dominick Baier’s UserNameWSTrustBinding. This was available in WIF on 4.0 but did not make it into 4.5, so Dominick added it to his Thinktecture.IdentityModel NuGet package.

Oh, and don’t expect the sample to be production ready. In fact, it won’t even work out of the box when you run it. You will have to configure several URL’s to match you environment. And as said, you’ll have to configure ADFS using the tutorial I mentioned.

Of course, I haven’t totally given up on the OWIN route, nor am I finished outlining the different delegation flavors. So stay tuned, because there’s more to come on coding identity federation and delegation!