How To Build and Deploy a Secure Serverless Application in Azure
Updated: Jan 14
Hello and welcome to the video on building and deploying a secure, serverless application using Azure Functions, API Management and Active Directory B2C.
I’m gonna show you how to create a simple ASP.NET Function App using Visual Studio 2019. But you can use Visual Studio Code if you like. Just make sure you’ve got dotnet core version 3 installed.
I’ll also show you how to set up basic build and release pipelines in Azure Devops. These will continuously build an ASP.NET Core Function App and deploy it to Azure.
I’ll show you how to set up an Azure API Management service using the new serverless consumption mode. API Management is a fully managed service that allows you to build an API gateway for your serverless microservices. The consumption tier is great because it offers built-in high availability, auto-scaling and a pay-as-you-go billing model in true serverless style.
The consumption model does have a restricted set of features compared to other tiers and is deployed on a shared infrastructure but it’s definitely worth a look. It could save you a lot of money. Have a look at the Azure website for a more detailed comparison with other tiers.
Once I’ve set up API Management, I’ll show you how to configure it to validate a JWT token that’s been issued by an Active Directory B2C tenant. You’ll see how easy it is to set up this serverless microservice architecture using all the great services that Azure provides.
If you haven’t set up a B2C tenant before then check out my previous video on how to do it. I’ll be using the tenant that I created in that video for this demo.
Before we get stuck into the code let’s take a quick high-level look at the solution architecture. As you can see we’ve got a Function App that serves as our api, which is hidden behind an API Management Gateway. This protects access to the api by ensuring that any requests from a client like a single page application contain a valid JWT token issued by our B2C tenant.
In this demo we won’t be building a single page application. Instead we’ll be using Postman to retrieve tokens from the B2C tenant and to send authenticated api requests to the gateway. Microsoft does provide an excellent library for integrating a single page app with the B2C tenant called MSAL.js but in this tutorial I wanted to show you how to make the raw http requests so you gain a better understanding of how this library works under the hood.
Ok it’s time to get started and the first thing we’ll do is set up a new project in Azure Devops. It’s best practice to set up a walking skeleton devops deployment cycle at the beginning of any project. That way you can be sure that any code you write can be built and deployed from the very start of the project.
Click on Create new project and give it a name. Once again I’ll be using a fictional company called Travel Pigeon.
Now that the project has been set up, let’s create a new git repo for the function app that we’re going to create.
Navigate to Repos and File and select New Repository from the dropdown. Give it a name and check the add readme. I’m going to add a gitignore file using the built-in VisualStudio template.
Once that’s set up let’s clone the new repo locally.
Click on the Clone button in the top-right hand corner and copy the git repo link.
Next open a terminal window, navigate to your desired location and clone the repo. This will have created a new directory that we can ‘cd’ into.
We’re ready to create the function app so head over to Visual Studio (or Visual Studio Code) and create a new project.
Search for the Azure Function template, which might already be in your recent templates list. Otherwise just search for it using the search bar.
Give your app a name and enter the path to the git repo that you just cloned.
I’m going to choose to place the solution and project in the same directory.
Click Create. On the next page you need to select the trigger for your Azure Function. There are quite a few to choose from but we’re going to use an Http trigger for this example. This means that our function will run in response to an http request that comes in from a client.
To run the function locally, visual studio needs a running instance of the Azure storage emulator. If you don’t already have this installed you can download it from the Microsoft website. There are community maintained versions that run on Linux too.
Make sure that the Function Authorization level is selected. This means that your http triggered function can’t be invoked unless an api key is present in the http request. If you were to select the Anonymous level an api key wouldn’t be required. The admin level is for running the function using a master key that can’t be revoked so be careful as this key grants elevated permissions to the caller.
Click Create and Visual Studio will generate a Function App template for us.
Let’s start by renaming the default function to something more specific. If you rename the file in Visual Studio it’ll do some handy refactoring for you. We just need to update the Function Name attribute manually.
Ok let’s run the function. This starts up the runtime host which connects to the storage emulator in the background. Once it’s up and running it’ll write the function urls to the console window for any functions that you’ve written.
We can copy the provided function url and paste it into a new postmnas request. If you don’t have Postman you can download it for free from their website. It’s a really useful tool for testing apis.
We need to provide a name parameter as that’s what our default function requires. If we send this request, our function app will return a 200 and a greeting message in the response body as expected.
Now you might be wondering why we didn’t need to provide an api key here since we specified the Function authorization level earlier. It’s because Visual Studio knows that we’re running the function locally using the storage emulator so it allows all requests to come through. It’s as though we enabled anonymous access.
This makes development and testing locally a lot easier. Once the function is deployed the runtime enforces the authorization so an api key would then be required as we’ll see when we deploy the app.
If we look at the runtime host window you’ll see a log of the request we just made.
I’m going to press Ctrl + C and shut down the host.
This automatically stops the app in Visual Studio.
Now we’re ready to deploy the function to Azure. But first we need to push the changes we’ve made to the git repository we set up earlier.
Open up your terminal window and stage all changes and create a commit. I’m using the git cli here but you could use a git UI if you’re more comfortable with that.
Push the commit to master. Obviously we wouldn’t push directly to master in real-life, instead we’d created a new branch and create a Pull Request. But it’s ok for this demo.
If we return to the Azure Devops repo and refresh the page you’ll see the new function app files appear.
Now that our code has been committed to source control, we’re ready to set up a build pipeline. This pipeline will be responsible for building the code, running any tests, and publishing a release artifact to a local folder on the build server.
The artifact will be picked up by a Release pipeline that we’ll create separately. This keeps our build and release pipelines nicely decoupled and means we can deploy the same artifact to multiple environments and control how and when this is done.
Click on New Pipeline and Azure Devops will ask you where your code is stored. We pushed ours to an Azure repo so select that. Select the repo you created and Devops will generate a pipeline YAML file for you. The only thing we need to add to this auto-generated pipeline is a step for publishing the artifact. Scroll to the bottom of your YAML file and make sure you have a Publish step that looks like this.
When you’re done, click Run and Azure will run your build pipeline. Once it’s complete you’re ready to create the Release pipeline that will pick up the build artifact and deploy it to Azure.
But before you can do that you need to first setup a new Function app resource in Azure. This is so the release pipeline has a real place to deploy the artifact to.
I’ve opened the Azure portal, and I’m going to click Create a resource and select Function App.
Choose a resource group and give your function app an instance name, which needs to be globally unique.
We’re using the .NET Core runtime but you can also write functions in other programming languages and frameworks. I’ll choose the West Europe region.
On the hosting step you need to create a storage account or select an existing account. This is by the runtime to store meta data and manage triggers and logging. I’m going to create a new storage account for this function app.
I’m going to use the Windows operating system but you could use Linux if you like.
Finally you need to choose a Plan. Since we’re building a serverless application choose the Consumption plan. This does mean the app will be prone to cold starts if it’s not being used. You can use the Premium or a dedicated plan if you want to significantly reduce any latency associated with cold starts or want to use an existing App Service Plan.
Let’s enable app insights, ignore the Tags step and then click create.
Once the function app is deployed you can navigate to it using the link provided.
With the function app resource ready in Azure we can return to Devops to set up the Release pipeline.
Recall that we’ve already set up a build pipeline that is responsible for creating the build artifact. Now we need to create a Release pipeline to deploy the artifact to Azure.
Click on Releases and New Pipeline. We’ll use the App Service template because a Function App is a special really kind of App Service. We’ll just create one deployment stage in this example that I’ll call Development.
In a real-life scenario you would create multiple stages for different environments including production. You could then either create an app service slot for each environment in Azure or create separate function apps depending on your requirements.
We need to link the artifact created by the build pipeline to this release pipeline so devops knows what to deploy. Let’s also enable a continuous deployment trigger so that a new release is created every time a new build is created.
This is useful for development and test environments but you might not want to do this for a production environment unless you’ve got some thorough automated tests in place.
Next we need to configure the steps involved in the release process. Click on the link found on the Development stage step and you’ll see that a deployment job has already been added. You just need to configure it so the pipeline knows where to deploy the build artifact.
Choose either a service principal or azure subscription from the drop down list. You may need to authorise Azure Devops to access your Azure account if you haven’t already done so.
Under App Type choose Function App on Windows.
Devops will pre-populate the App Service name drop-down so you can choose the function app you created earlier.
Click save and that’s the Release pipeline complete.
You could choose to Create a release now but for this demo we’ll try out the continuous deployment trigger. Head back to the Build pipeline, click Queue and then Run a build.
Once the build has finished, a new Release will automatically be created and a deployment will be triggered.
Head back to the Azure portal and refresh the function app page. You’ll see that the function you created now appears under Functions.
Let’s call this deployed function using Postman.
First grab the function URL from the portal. Click on Get Function URL and copy the url provided. Paste this into a new Postman request. Notice that Azure has provided a function api key for you to use since we selected the Function authorization level. We need to add a name query parameter as before.
The function returns a 200 with a greeting in the response body as before.
If we remove the api key from the request you can see that we get a 401 Unauthorised response instead.
Let’s remind ourselves of the solution architecture diagram. So far we’ve set up and deployed a function app and we already have a B2C tenant. The final piece in the architecture is to set up the API Management gateway.
Head back over to the azure portal and create a new resource. This time search for API management and click create.
Give it a name and select a resource group, location and an Organisation Name. API Management is can be used as a portal for publishing APIs to developers, external partners and customers so entering an Organisation Name makes it clear who you are.
We’re going to select the Consumption pricing tier which is the serverless mode. This has a 3 9s SLA so is highly available.
Once our API Management service is created navigate to it using the provided link. We need to expose the api app we’ve created in the gateway and do to this we click on the APIs section.
Azure helpfully provides some quick starts and there’s one for Function Apps that we’re going to use. The quick starts will guide you through the process of adding your api to the API gateway.
Azure will display the base url for the api gateway. This is the base URL that clients will use to call your api.
Once your api has been added, Azure will show you the endpoints that it’s found in your API. You can drill down into each endpoint and set up policies that modify requests and responses. You can also add policies that apply to All Operations which is what we’ll do soon.
First there’s a couple of settings changes we need to make. Click on Settings.
Copy the Base URL to your clipboard as we’ll need that.
Then remove the subscription requirement. If left enabled a subscription key would be needed to access your api. Subscriptions are a great way to publish apis to customers and other developers and are used to create commercial api products.
But in this demo we don’t need our clients to have a subscription. Instead we want them to have a valid JWT token issued by our B2C tenant.
Click Save and then return to Postman so we can test the gateway.
Paste the base url you copied into a new request. Append the name of your function along with the function key that you used previously. For the function I’m using I also need to add a name parameter.
Click Send and as expected we get a 200.
Let’s try removing the function key from the request and see what happens. As you can see we get a success result. Why is that?
This is because Azure automatically appends a valid function key to the request when the request comes through the gateway. This was configured for us when we used the quick start to add the function app to the gateway.
This is ok for our purposes because we want to expose the function api without needing to provide an api key. Instead we want to enforce the presence of a valid JWT token. Let’s now look at how to do that.
Back in the Azure portal, switch to your B2C tenant using the switch directory button. Navigate to the B2C service.
Click on App Registrations. I’ve got an existing app setup that represents an ASP.NET Core web app that I set up in my last video tutorial.
I’m going to assume this app now represents a single page application. Since we’re not creating one in this tutorial you can think of this app as representing Postman instead.
To do that we need to register an app in the B2C tenant that represents our new API Management gateway.
Click on New registration and give it a name representing the API.
Leave the supported account types on any organizational directory or any identity provider because we want to allow anyone to sign up (not just users in a specific Active Directory tenant).
We don’t need to add any redirect uris because this app registration won’t be directly involved in the OAuth 2 authentication flow.
Instead this app registration will be responsible for exposing an api so we need to define some permissions or scopes that a client can request as part of the authorization flow.
Click on Expose an API and then change the Application ID URL to something a bit more meaningful. This url is used as a prefix for any scopes we create.
Let’s add two scopes for the api.
One representing read access… and one representing write access. Clients will be able to request either or both when requesting access tokens.
Since we’ll be using the other app registration in our authentication flow we need to give it access to the scopes we just created.
Head over to that app registration and click on API permissions.
Add both permissions that we linked to the API app registration.
And be sure to Grant consent so that Azure knows the app registration has permission to access the scopes. You’ll need to use your Azure admin account to grant this permission. You should see a confirmation screen where you can confirm that the app registration is allowed access to the api scopes.
Wait until the permissions have been granted which might take a minute or two.
Now that we’ve set up the B2C tenant to grant tokens that can be used to access our API, it’s now time to configure the API Management Gateway to validate these tokens.
Back in your main Azure tenant, navigate to the APIs section on the API Management resource.
Select All operations and then expand the policies within the Inbound processing section. Here you can define specific policies that can be used to change the behaviour of your APIs.
Policies are a collection of Statements that are executed sequentially on the request or response of an API.
I’m going to paste in a policy before the base attribute that instructs the gateway to validate a JWT token.
We just need to add in the missing values for the open-id config, audience and issuer. Fortunately we can find these values in the B2C tenant.
Copy the application id of the api app registration and paste it into the audience tag. This means the policy will only allow tokens that have been granted an audience with the API.
To retrieve the other values you need, head over to the signin User flow that you exists for your B2C tenant. I set this up in my last video tutorial. If you click on Run user flow you will find the open-id config url that the policy requires. Click on the link to open the config in a new browser window.
Copy the url and paste it into the open-id config url value.
You’ll also find the issuer value for the b2c directory on the config page. Paste this into the issuer tag.
Now that you’ve set up a policy to validate a JWT token issued from your B2C tenant it’s time to test it out using Postman.
Let’s return to the last request which called our function using the gateway url.
If we run it again this time we’ll see a 401 response with a message telling us that a token is required.
Let’s use Postman to retrieve an access token from our B2C identity service.
Open up a new request.
Return to the open-id config page that you opened in the browser window.
Copy the authorization endpoint value and paste it into the Postman request URL.
Finally we add to add some query parameters to the request to retrieve a token.
First add a client id. This is the application id of the app registration that you want a token from. You can retrieve this from the app registration on the portal. It’s the original one not the new api app registration that you require.
Next add a parameter called response_type and use the value token. You can optionally request an id token using this parameter too.
Add a redirect url parameter which is where you want the client to be redirected to after successful authentication. This url needs to be registered in the B2C tenant under the registered redirect urls.
I’m going to add a state parameter. This can be any value and it’ll be returned in the url after successful authentication so you can use it for whatever you like.
Next add a none parameter. This is a value that will be returned as a claim when an id token is requested. It’s used to mitigate against token replay attacks. It is only required when an id_token is requested which isn’t the case for my request but I wanted to show you anyway.
Next you need to add a parameter called scope that specifies the permissions you want to request.
You’ll recall that we set up two scopes for our api, read and write. Let’s request both scopes.
The final parameter we need to add is called response mode. If you’re just requesting an access token this value can be left out and it will default to the value of “query”. However if you request an id token in the response type field then it needs to be fragment. I’m including it here just so you’re aware of the difference but again I’m only requesting an access token.
That’s the setup of the request complete so rather than running it in postman I’m going to copy it and paste it into a browser window. This is because the implicit flow that we’ve configured will redirect the request to a login prompt.
I’ll log in using an account that I’ve already registered with the tenant.
Once you’ve logged in you’ll be automatically redirected to the url specified in the request.
In our case it’s this page which automatically decodes the JWT token for us. As you can see in the address bar the value we used in the state parameter has been returned in the response as well as the access token.
Copy the access token as you’ll need that to make the api gateway request. Before we return to postman I just want to show you that the scopes we requested are also returned in the token.
Ok let’s see if the token works. Open up Postman and in the request that previously failed, in the Authorization section, make sure Bearer token is selected as the Type and then paste the token in the token field.
Click Send and we once again get a 200.
I hope you’ve enjoyed this video. If you have make sure you click subscribe as I’ll be posting videos every week on similar topics.