ASP.NET Core, Swagger and seamless integration with Azure B2C

Recently I've made my very first real project using ASP.NET Core and I must say it looks fabulous! Since I was working on a common API, I decided(as always) to introduce an interface via Swagger. There was an additional feature - I had to use Azure B2C as my authentication service. It was more or less painful, yet after all my struggles the whole integration is brilliant. Here is a short receipt to do it on your own(there're different examples, but I find most of them lacking some small details, which make the whole picture). 

ASP.NET project

This part is simple - create a basic ASP.NET Core project using an API template:

Once we have a project created, one more thing is needed - a package, which will generate a Swagger definition. I decided to try out Swashbuckle.AspNetCore:

Now we have to configure it.

Swagger configuration

To configure Swagger, you have to do 2 things:

  • add a Swagger service
  • tell the application to use it

To add(and configure) a Swagger service go to Startup.cs file and find ConfigureServices method. For the basic functionality it should look like this:

/
public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc();
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new Info { Title = "Test API", Version = "v1" });
	});
}

Now tell the app to use Swagger:

/
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}

	app.UseMvc();
	app.UseSwagger();
}

Let's try to test it. Press F5 and go to /swagger endpoint and...

Bang, it-does-not-work. Let's try to quickly fix this:

/
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}

	app.UseMvc();
	app.UseSwagger();
	app.UseSwaggerUI(options =>
	{
		options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API");
	});
}

Now when we start an application, going to /swagger endpoint should redirect us to the definition:

Now let's secure our API!

Securing an API

To secure an API we'll add [Authorize] attribute like this:

/
[Authorize]
[Route("api/[controller]")]
public class ValuesController : Controller
{
}

Now when calling an API method, we'll get HTTP 401 response. To enable B2C token validation we need to configure JWT token options like this:

/
public void ConfigureServices(IServiceCollection services)
{
	services.AddAuthentication(options =>
		{
			options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
		})
		.AddJwtBearer(jwtOptions =>
		{
			jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
			jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
		});
	services.AddMvc();
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new Info { Title = "Test API", Version = "v1" });
	});
}
/
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}

        app.UseAuthentication();
	app.UseMvc();
	app.UseSwagger();
	app.UseSwaggerUI(options =>
	{
		options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API");
	});
}

Additionally configure your options in appsettings.json:

/
"AzureAdB2C": {
	"Tenant": "tenantname.onmicrosoft.com",
	"ClientId": "client_id",
	"Policy": "policy_name"
}

But how can we automate sending a bearer token which each request requiring authentication in Swagger?

Enabling OAuth2 authentication in Swagger

To enable authentication using OAuth2 in Swagger, we have to change ConfigureServices method a little:

/
public void ConfigureServices(IServiceCollection services)
{
	services.AddAuthentication(options =>
		{
			options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
		})
		.AddJwtBearer(jwtOptions =>
		{
			jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
			jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
		});
	services.AddMvc();
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new Info { Title = "Test API", Version = "v1" });
		c.AddSecurityDefinition("oauth2", new OAuth2Scheme
		{
			Type = "oauth2",
			Flow = "implicit",
			AuthorizationUrl = $"https://login.microsoftonline.com/{Configuration["AzureAdB2C:Tenant"]}/oauth2/v2.0/authorize?p={Configuration["AzureAdB2C:Policy"]}&response_mode=fragment",
			Scopes = new Dictionary<string, string>
			{
				{"openid", "OpenID"},
				{$"https://{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:AppIDURI"]}/read.access", "Access Test API" }
			}
		});
	});
}

As you can see, we've added security definition for OAuth2 in Swagger definition. We defined the type as oauth2 and flow as implicit(as suited for our scenario). AuthorizationUrl is the URL of our B2C endpoint, which defines which policy we'd like to use and what kind of response we expect. The most important thing are the Scopes, which tell us what we can access. When you run your application, you'll see that integration is enabled:

The last thing we need to do is to configure our application in Azure B2C.

Configuring Azure B2C

Once you create an Azure B2C tenant, you have to register an application and define how one can access it. Go to your tenant and create a new application:

Note that port has to match port under which your application runs locally. Now with an application created in Azure B2C we can obtain ObjectId(here called ApplicationId) and test our integration:

Now when I click Authorize, after providing my user and password, I'll get following error:

Auth error

{"error":"invalid_request","error_description":"AADB2C90205:+This+application+does+not+have+sufficient+permissions+against+this+web+resource+to+perform+the+operation.\r\nCorrelation+ID:+c0657981-668b-40c6-a77d-02cb9061956c\r\nTimestamp:+2018-04-09+09:15:09Z\r\n","state":"TW9uIEFwciAwOSAyMDE4IDExOjE1OjA2IEdNVCswMjAwIChDZW50cmFsIEV1cm9wZWFuIERheWxpZ2h0IFRpbWUp"}

We have to grant our application missing permissions. But how can we achieve this?

Missing permission

As you can see in our code, we defined two scopes: openid and read.access. Normally we'd like to use openid, but Azure B2C requires providing both openid and other scope. For now we're missing read.access scope in our application. Let's add it. In Azure Portal go to Published scopes:

Now add a new scope read.access:

To finish it go one section up to API access and add new value:

Let's test our integration now:

However when trying to execute a method in our API, we're still getting HTTP 401. Something's missing. It turns out, that we're missing a security requirement setting, which handles injecting a token into header. Consider following ConfigureServices method:

/
public void ConfigureServices(IServiceCollection services)
{
	services.AddAuthentication(options =>
		{
			options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
		})
		.AddJwtBearer(jwtOptions =>
		{
			jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
			jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
		});
	services.AddMvc();
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new Info { Title = "Test API", Version = "v1" });
		c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
		{
			{ "oauth2", new[] { "openid", $"https://{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:AppIDURI"]}/read.access" } }
		});
		c.AddSecurityDefinition("oauth2", new OAuth2Scheme
		{
			Type = "oauth2",
			Flow = "implicit",
			AuthorizationUrl = $"https://login.microsoftonline.com/{Configuration["AzureAdB2C:Tenant"]}/oauth2/v2.0/authorize?p={Configuration["AzureAdB2C:Policy"]}&response_mode=fragment",
			Scopes = new Dictionary<string, string>
			{
				{"openid", "OpenID"},
				{$"https://{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:AppIDURI"]}/read.access", "Test API" }
			}
		});
	});
}

Now you should see sweet HTTP 200!

Summary

Integrating Swagger with Azure B2C looks like a nice idea to avoid passing a bearer token manually. What is more you can incorporate your authentication process into testing, so you now that e.g. assigned scopes allow or block access in the right way. I strongly encourage you to play this feature a little bit so you how powerful tool it is.

Event Hub Capture as a service

For some reason I've had hard times searching for proper use cases when considering Event Hub Capture feature. On the first glance it seems as a reasonable functionality, which can be used for many different purposes:

  • in-built archive of events
  • seamless integration with Event Grid
  • input for batch processing of events

On the other hand I haven't seen any use cases(I mean - besided documentation) using Capture in a real scenario. What's more, the price of this feature(roughly 70$ per each TU) could be a real blocker in some projects(for 10TUs you pay ~200$ monthly for processing of events, now add additional ~700$ - my heart bleeds...). So what is Capture really for?

Compare

Experience shows, that never ever disqualify a service until you compare it with other cloud components. Recently my team has been struggling with choosing the right tool to process messages in our data ingestion platform. To be honest it's not that obvious scenario as it looks. You could choose:

  • EH Capture with Azure Data Lake Analytics
  • Direct processing by Azure Functions
  • Azure Batch with event processors
  • Stream Analytics
  • VM with event processors
  • EH Capture + Azure Batch

Really, we can easily imagine several solutions, each one having pros and cons. Some solutions seems to be more for real time processing (like Stream Analytics, VM with event processors), some require delicate precision when designing(Functions), some seem to be fun but when you calculate the cost, it's 10x bigger that in other choices(ADLA). All are more or less justified. But what about functionality?

IT'S JUST NOT THERE

Now imagine you'd like to distribute your events between different directories(e.g. in Data Lake Store) using dynamic parameter(e.g. a parameter from an event). This simple requirement easily kills some of solution listed above:

  • ADLA has this feature in private preview
  • Stream Analytics doesn't have this even in the backlog

On the other hand, even if this feature was available, I'd consider a different path. If in the end I expect to have my events catalogued, Azure Batch + EH Capture seems like a good idea(especially that it allows to perform batch processing). This doubles the amount of storage needed but greatly simplifies the solution(and gives me plenty of flexibility).

There's one big flaw in such design however - if we're considering dropping events directly to an Data Lake Store instance, we have to have them in the same resource group(what doesn't always work). In such scenario you have to use Blob Storage as a staging scenario(what could be an advantage with recent addition of Soft Delete Blobs).

What about money?

Still Capture is more expensive than a simple solution using Azure Functions. But is it always a case? I find pricing of Azure Functions better if you're able to process events in batches. If for some reason you're unable to do that(or batches are really small), the price goes up and up. That's why I said, that this requires delicate precision when designing - if you think about all problems upfront, you'll be able to use the easiest and the simplest one solution.

Conclusion

I find Capture useful in some listed scenarios, but the competition is strong here. It's hard to compete with well designed serverless services, which offer better pricing and often perform with comparable results. Remember to always choose what you need, not what you're told to. Each architecture has different requirements and many available guides in most cases what cover your solution.