{"id":5,"date":"2023-01-11T19:57:26","date_gmt":"2023-01-11T18:57:26","guid":{"rendered":"https:\/\/d.r5.wbsprt.com\/matejbobaly.com\/?p=5"},"modified":"2023-01-11T19:57:26","modified_gmt":"2023-01-11T18:57:26","slug":"securing-service-to-service-http-triggered-azure-functions-using-client-certificates","status":"publish","type":"post","link":"https:\/\/matejbobaly.com\/?p=5","title":{"rendered":"Securing service-to-service HTTP-triggered Azure Functions using client certificates"},"content":{"rendered":"\n<pre class=\"wp-block-verse\">\nThe code from this post is available from GitHub: <a href=\"https:\/\/github.com\/matejbob\/ClientCertificateAuth.git\" data-type=\"URL\" data-id=\"https:\/\/github.com\/matejbob\/ClientCertificateAuth.git\">https:\/\/github.com\/matejbob\/ClientCertificateAuth.git<\/a> .\n<\/pre>\n\n\n\n<h2>Introduction<\/h2>\n\n\n\n<p>Beyond any doubt, <em>Azure Functions<\/em> became a very popular serverless solution. Simple development model and smooth integration with other components of architecture using out-of-the-box triggers and bindings rank among the biggest strengths of Azure Functions.<\/p>\n\n\n\n<p>For the purposes of this article, Azure Functions can be divided into two categories based on the type of the client they&#8217;re intended to be consumed by:<\/p>\n\n\n\n<ul><li><strong>Web or mobile API<\/strong> (e.g., REST API or GraphQL): one or more Azure Functions comprise an API consumed by a user-facing front-end <\/li><li><strong>Service-to-service<\/strong>: Azure Function is a part of bigger architecture and as such it is invoked by other components of the system or by an external service<\/li><\/ul>\n\n\n\n<p>Distinction between these two categories is key when deciding on the authentication method. In the first case, identity of the user comes into play. If authentication is required, typically either an <strong>Identity Provider<\/strong>, explained below, or a custom authentication mechanism is leveraged to sign in the person using the application. On the other hand, in the second case, it&#8217;s identity of the calling component that is involved and, unlike the former, this is when certificate-base authentication is practically applicable. In other words, certificate-based authentication is usually not a feasible option for user-facing APIs.<\/p>\n\n\n\n<p>Another important division of Azure Functions when weighing up authentication alternatives is the type of the Azure Function trigger. In this regard, Azure Function can be:<\/p>\n\n\n\n<ul><li><strong>HTTP-triggered<\/strong>: Function is triggered by an HTTP request<\/li><li><strong>Other <\/strong>(e.g. using Timer Trigger, Azure Service Bus Trigger, Azure Cosmos DB Trigger etc.) <\/li><\/ul>\n\n\n\n<p>As certificates pertain to HTTP, it&#8217;s obvious that it&#8217;s only this category of Azure Functions that can take advantage of certificate-based authentication.<\/p>\n\n\n\n<p>From the argument given above, it follows that, in practice, service-to-service HTTP-triggered Azure Functions are suitable for certificate-based authentication. Remarkably, this type of Azure Functions is one of the most common.<\/p>\n\n\n\n<h2> Summary of alternatives <\/h2>\n\n\n\n<p>When it comes to securing an service-to-service HTPP-triggered Azure Function, these are basic options to consider:<\/p>\n\n\n\n<ul><li><strong>Anonymous<\/strong>: no authentication\/authorization is performed, the Function may be called by anybody without any constraints<\/li><li><strong>API key<\/strong>: in order to invoke the Function caller must <em>include secret API key<\/em> in its HTTP request<\/li><li><strong>Identity Provider<\/strong>: <em>App Service <\/em>hosting the Function can use an OAuth2-based provider, e.g. Azure AD, to enforce authentication<\/li><li><strong>IP filter<\/strong>: when the hosting App Service is a part of Virtual Network in Azure, network traffic can be limited to specific source IP addresses, IP filtering should not regarded as a reliable security mechanism because of issues like IP address spoofing <\/li><li><strong>Client certificate<\/strong>: leveraging public-key cryptography and TLS, the caller presents a TLS certificate containing public key to the Function host and <em>proves to posses corresponding (secret) private key<\/em>, based on this proof the Function host concludes that the caller is the subject described in the presented certificate<\/li><\/ul>\n\n\n\n<p>The first three approaches are all well-documented on the Internet. This post focuses on the last option, namely authentication using client certificates.<\/p>\n\n\n\n<h2>Advantages of using client certificates<\/h2>\n\n\n\n<p>Authentication based on client certificates stands somewhere between the API key authentication and using an identity provider when robustness, flexibility, scalability but also complexity are considered. It should be regarded as a more secure alternative to API keys involving little additional effort rather than a fully-fledged replacement for identity provider. <\/p>\n\n\n\n<p>In my experience, teams often choose API keys to secure Azure Functions. The main reason is simplicity of this technique. Caller includes an API key know to both sides of the communication in its HTTP requests, the Azure Function host verifies that it&#8217;s the expected API key and if this verification succeeds, it handles the request.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"606\" height=\"121\" src=\"https:\/\/d.r5.wbsprt.com\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/client-certificate-azure-function.png\" alt=\"\" class=\"wp-image-37\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/client-certificate-azure-function.png 606w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/client-certificate-azure-function-300x60.png 300w\" sizes=\"(max-width: 606px) 100vw, 606px\" \/><figcaption><strong>API key authentication<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>The important aspect of API key authentication is the fact the API key travels over the wire and, as a result, possibility of the API key getting compromised can not be ruled out. Users with sufficient permissions to read the API key value on the server side may also cause a leak of the key.<\/p>\n\n\n\n<p>As opposed to the API key authentication, if client certificates are used, the secret which is the private key in this case is neither sent from to the caller to the Function host nor is it stored in the Function host. Possession of the private key associated with a specific TLS certificate is instead proven by the caller using <em>mutual TLS<\/em> or <strong>mTLS<\/strong> in short. This eliminates the possibility of the secret information getting leaked from the Function host. The function host is only aware of the TLS certificates that are permitted to invoke the Functions it hosts. Note plural here, unlike the single API key, multiple client certificates may be <em>pinned <\/em>and trusted by the Function host which allows authenticating and authorizing multiple identities corresponding to individual certificates. <\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"621\" height=\"121\" src=\"https:\/\/d.r5.wbsprt.com\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/client-certificate-azure-function2.png\" alt=\"\" class=\"wp-image-38\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/client-certificate-azure-function2.png 621w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/client-certificate-azure-function2-300x58.png 300w\" sizes=\"(max-width: 621px) 100vw, 621px\" \/><figcaption><strong>Client certificate authentication<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>We&#8217;re going to show that client certificates allow securing only selected endpoints while keeping the rest of them public, i.e., accessible anonymously. It should be relatively easy to expand this access control and manage access to endpoints per identity. However, this technique is not discussed further in this article.<\/p>\n\n\n\n<h2>Disadvantages of using client certificates<\/h2>\n\n\n\n<p>Identity providers supporting the standard <strong>OAuth 2.0 client credentials flow<\/strong> present a more advanced alternative to client certificates featuring higher flexibility, robustness and more fine-grained access control. Identity provider should be the first choice when the set of consumers of the Azure Function is dynamic, meaning that new consumers need to be added and existing consumers need be removed often. Also Identity provider is a no-brainer when the number of consumers is high or when role-base authorization rules are anything but simple. As it has already been pointed out, Identity provider affects complexity which can be an overkill when none of the conditions above holds true. When the set of consumer is small and relatively static, client certificates present an attractive option.<\/p>\n\n\n\n<h2>Sample code<\/h2>\n\n\n\n<p><strong>Visual Studio 2022<\/strong> should be used to follow the sample code. Create a new project using <strong>Azure Functions <\/strong>template:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_azure_function_template.png\" alt=\"\" class=\"wp-image-40\" width=\"718\" height=\"478\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_azure_function_template.png 1014w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_azure_function_template-300x200.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_azure_function_template-768x511.png 768w\" sizes=\"(max-width: 718px) 100vw, 718px\" \/><figcaption><strong>Create a new project<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>Name new project <strong>ClientCertificateAuth<\/strong>:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_project-1.png\" alt=\"\" class=\"wp-image-42\" width=\"724\" height=\"482\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_project-1.png 1014w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_project-1-300x200.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_project-1-768x511.png 768w\" sizes=\"(max-width: 724px) 100vw, 724px\" \/><figcaption><strong>Configure new project<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>Select <strong>.NET 6.0 Isolated<\/strong> runtime and <strong>Anonymous <\/strong>authorization level:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_runtime.png\" alt=\"\" class=\"wp-image-43\" width=\"725\" height=\"483\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_runtime.png 1014w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_runtime-300x200.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/vs2022_runtime-768x511.png 768w\" sizes=\"(max-width: 725px) 100vw, 725px\" \/><figcaption><strong>Runtime and authorization level<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>After Visual Studio creates new project, delete the default source file <strong>Function1.cs<\/strong>.<\/p>\n\n\n\n<p>In the code below we define two Azure Functions that comprise our service. The first Function named <em>PublicFunction <\/em>should be callable anonymously as indicated by <strong>AnonymousAccessAttribute<\/strong>. By default, when this attribute is not applied, the Function is secured using client certificates. Therefore the second function named <em>SecuredFunction<\/em> uses client certificate authentication.<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/matejbob\/84de6d70b52b9ce25064449ec69a3d4c.js\"><\/script>\n\n\n\n<p> <strong>AnonymousAccessAttribute<\/strong> definition:<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/matejbob\/27c7d4887b75cfa4caf00d2de2531eb9.js\"><\/script>\n\n\n\n<p>Now we generate two self-signed TLS certificates that we&#8217;ll use to test the solution. The first one named <strong>allowed.pfx<\/strong> will be allowed to invoke the secured Function whereas the second one named <strong>denied.pfx<\/strong> won&#8217;t. An easy way to generate the certificates is using Powershell. In order to generate the first certificate and display it&#8217;s unique thumbprint following commands must be <span style=\"text-decoration: underline;\">run under an administrator account<\/span>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$filename = \"allowed.pfx\"\n$pwd = ConvertTo-SecureString -String \"Pa@@word123\" -Force -AsPlainText\n$cert = New-SelfSignedCertificate -Subject TestSubject -DnsName localhost -NotAfter (Get-Date).AddYears(10)\nExport-PfxCertificate -Cert $cert -FilePath $filename -Password $pwd\n$cert.Thumbprint<\/code><\/pre>\n\n\n\n<p>The output of the commands will look like the following:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"457\" height=\"79\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/thumbprint.png\" alt=\"\" class=\"wp-image-63\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/thumbprint.png 457w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/thumbprint-300x52.png 300w\" sizes=\"(max-width: 457px) 100vw, 457px\" \/><figcaption><strong>Generated certificate thumbprint<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>The other certificate <strong>denied.pfx<\/strong> file should be generated in the same way. Make sure to remember the password used to generate the certificates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$filename = \"denied.pfx\"\n$pwd = ConvertTo-SecureString -String \"Pa@@word123\" -Force -AsPlainText\n$cert = New-SelfSignedCertificate -Subject TestSubject -DnsName localhost -NotAfter (Get-Date).AddYears(10)\nExport-PfxCertificate -Cert $cert -FilePath $filename -Password $pwd\n$cert.Thumbprint<\/code><\/pre>\n\n\n\n<p>The <strong>PFX <\/strong>files we created contain both public certificate and corresponding private key which is why they are stored in encrypted form using the password (sometimes call <em>passphrase<\/em>).  <\/p>\n\n\n\n<p>Next we need to create a class that will compare the thumbprint of the certificate(s) included in the request to the known valid thumbprints. Note, multiple certificates may be included in the request. For simplicity sake, we hardcode the allowed certificate&#8217;s thumbprint in the class but the list of allowed certificate thumbprints could be easily stored externally and obtained in the run time. <strong>CertificateValidator<\/strong> class is defined by the following code:<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/matejbob\/394c6e313f786b6acbe9d823550daeed.js\"><\/script>\n\n\n\n<p><strong>CertificateValidator <\/strong>object will be used in a custom middleware that will be plugged in to the request processing pipeline. The middleware will determine whether current request is targeting a secured Function. If so and a valid certificate is provided in the request, middleware will pass the request to the next stage of the request pipeline. Otherwise, request processing is stopped and<strong> HTTP status code<\/strong> <strong>401<\/strong> is returned. The middleware is defined by the following code. Note that reflection is used to identify the target method and to check if it is tagged by <strong>AnonymousAccessAttribute<\/strong>. Client certificate, or possibly multiple certificates, are contained in <strong>X-ARR-ClientCert <\/strong>request header set by the hosting App Service when mTLS is accomplished. As we&#8217;ll show later, this header must be set manually when testing locally. Create the file <strong>ClientCertificateMiddleware.cs <\/strong>containing the following code:<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/matejbob\/5e09fc53ab47b95bba42c89f87334b4c.js\"><\/script>\n\n\n\n<p>Now we just need to tell the Function host to add our middleware to the request processing pipeline. In order to that, modify <strong>Program.cs<\/strong> in the following way:<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/matejbob\/ca50d6e40dc718d31c8f38b977133a62.js\"><\/script>\n\n\n\n<p>This example is simplified in many aspects. Examples of how it could be enhanced include storing thumbprints of allowed certificates externally and caching them in the host and fine-grained access control using roles assigned to identities. Nevertheless it proves the concept in general. <\/p>\n\n\n\n<h2>Testing locally<\/h2>\n\n\n\n<p>When our Functions run in Azure, <strong>X-ARR-ClientCert<\/strong> header will be set if and only if the hosting Function App successfully completes mTLS which will guarantee that the request is really coming from the identity associated with given certificate. To test our solution locally, we&#8217;ll mock mTLS and directly include this header in our request. In order to achieve this, we need to extract the certificate part from our PFX files using the following Powershell command block executed from the directory containing your PFX files. Note that you&#8217;ll need to provide the password used during generation twice:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>foreach ($name in \"allowed\", \"denied\")\n{\n  Get-PfxCertificate -FilePath \"$name.pfx\" | Export-Certificate -FilePath \"$name.crt\" -Type CERT\n  $crtBytes = Get-Content \"$name.crt\" -Encoding Byte\n  &#91;System.Convert]::ToBase64String($crtBytes) | Out-File \"$name.crt.base64\"\n}<\/code><\/pre>\n\n\n\n<p>Two files named <strong>allowed.crt.base64<\/strong> and <strong>denied.crt.base64<\/strong> should be generated in the same directory. Open the latter and copy the content and use it as value of <strong>X-ARR-ClientCert<\/strong> header in new Postman request:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/local01-1024x291.png\" alt=\"\" class=\"wp-image-68\" width=\"1024\" height=\"291\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/local01-1024x291.png 1024w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/local01-300x85.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/local01-768x218.png 768w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/local01.png 1432w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption><strong>Request is unauthorized when sent with the denied<\/strong> <strong>certificate<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>When you start the project in Visual Studio and send this request, you should see the same result, the status code <strong>401<\/strong> and the error message should be received.<\/p>\n\n\n\n<p>Let&#8217;s validate the success path now. As a result of changing the value of X-ARR-ClientCert to the content of the file <strong>allowed.crt.base64,<\/strong> we should receive the following response indicating the success:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/success-1024x299.png\" alt=\"\" class=\"wp-image-70\" width=\"993\" height=\"290\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/success-1024x299.png 1024w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/success-300x87.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/success-768x224.png 768w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/success.png 1221w\" sizes=\"(max-width: 993px) 100vw, 993px\" \/><figcaption><strong>Request is authorized when sent with the allowed certificate<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>As we managed to locally verify that our authorization method works, it&#8217;s time to deploy the service to Azure and carry out a more realistic test.<\/p>\n\n\n\n<h2>Deployment<\/h2>\n\n\n\n<p>The first step is to create new <strong>Function App<\/strong> to host our Functions:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment01.png\" alt=\"\" class=\"wp-image-56\" width=\"335\" height=\"463\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment01.png 417w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment01-217x300.png 217w\" sizes=\"(max-width: 335px) 100vw, 335px\" \/><figcaption><strong>Create new Function App<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>The default values can be kept for all options but <strong>Runtime<\/strong>. Make sure that <strong>.NET 6 Isolated <\/strong>runtime is used:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment02-1.png\" alt=\"\" class=\"wp-image-59\" width=\"537\" height=\"662\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment02-1.png 741w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment02-1-243x300.png 243w\" sizes=\"(max-width: 537px) 100vw, 537px\" \/><figcaption><strong>Choose .NET 6 Isolated runtime<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>Once the Function App deployment completes, in the <strong>Configuration<\/strong> section, enable <strong>Optional Client certificate mode<\/strong>. This will ensure that no certificate will be required for public Functions while our custom implementation will enforce the verification of caller&#8217;s identity based on client certificate.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment03.png\" alt=\"\" class=\"wp-image-60\" width=\"581\" height=\"400\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment03.png 845w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment03-300x206.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2022\/12\/deployment03-768x528.png 768w\" sizes=\"(max-width: 581px) 100vw, 581px\" \/><figcaption><strong>Optional client certificate mode<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>Let&#8217;s test our Azure-hosted instance from Postman. The URL base can be determined from the <strong>Overview <\/strong>section of the Function App:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"251\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/baseurl-1024x251.png\" alt=\"\" class=\"wp-image-77\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/baseurl-1024x251.png 1024w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/baseurl-300x74.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/baseurl-768x188.png 768w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/baseurl.png 1505w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption><strong>Function App base URL<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>Copy and paste the URL and send a request to our public, anonymously accessible Function:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"259\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-public-postman-1024x259.png\" alt=\"\" class=\"wp-image-78\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-public-postman-1024x259.png 1024w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-public-postman-300x76.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-public-postman-768x194.png 768w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-public-postman.png 1424w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption><strong>Public Function called without a client certificate<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>As can be seen from the screenshot, a success response is received. Now, in an analogous way, we attempt to call the secured Function:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"290\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-secured-postman-1024x290.png\" alt=\"\" class=\"wp-image-79\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-secured-postman-1024x290.png 1024w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-secured-postman-300x85.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-secured-postman-768x218.png 768w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/azure-secured-postman.png 1430w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption><strong>Secured Function called without a client certificate<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>In line with expectations, the request fails with the Status Code 401. In order to make the request succeed we need to we need to prove the identity associated with the allowed certificate using mTLS. Open Postman&#8217;s settings, switch to the <strong>Certificates <\/strong>tab and click the <strong>Add Certificate<\/strong> button:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-settings01-1.png\" alt=\"\" class=\"wp-image-81\" width=\"627\" height=\"307\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-settings01-1.png 715w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-settings01-1-300x147.png 300w\" sizes=\"(max-width: 627px) 100vw, 627px\" \/><figcaption><strong>Add client certificate to Postman<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>The next step is to specify <strong>allowed.pfx<\/strong> to be used for calls to the base URL of you Function App instance:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-add-certificate-allowed.png\" alt=\"\" class=\"wp-image-83\" width=\"611\" height=\"343\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-add-certificate-allowed.png 718w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-add-certificate-allowed-300x168.png 300w\" sizes=\"(max-width: 611px) 100vw, 611px\" \/><figcaption><strong>Add the allowed certificate Postman<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>When the request is sent to the secured Function now, the response indicating successful invocation is received:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"263\" src=\"http:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-request-allowed-1024x263.png\" alt=\"\" class=\"wp-image-84\" srcset=\"https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-request-allowed-1024x263.png 1024w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-request-allowed-300x77.png 300w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-request-allowed-768x197.png 768w, https:\/\/matejbobaly.com\/wp-content\/uploads\/2023\/01\/postman-request-allowed.png 1424w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption><strong>Successful invocation of the secured Function<\/strong><\/figcaption><\/figure><\/div>\n\n\n\n<p>When <strong>denied.pfx<\/strong> is used with the request instead, the call is not authorized and the response is identical to the one received when no certificate was included.<\/p>\n\n\n\n<h2>GitHub repository<\/h2>\n\n\n\n<p>The code from this post is available from GitHub: <a href=\"https:\/\/github.com\/matejbob\/ClientCertificateAuth.git\" data-type=\"URL\" data-id=\"https:\/\/github.com\/matejbob\/ClientCertificateAuth.git\">https:\/\/github.com\/matejbob\/ClientCertificateAuth.git<\/a> . The solution contains another project named <strong>Client <\/strong>that serves as a sample C# client calling our Functions and including the client certificate. Note, the target URL must be changed in <strong>Program.cs <\/strong>in order to be able to perform calls to the Functions. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>The code from this post is available from GitHub: https:\/\/github.com\/matejbob\/ClientCertificateAuth.git . Introduction Beyond any doubt, Azure Functions became a very popular serverless solution. Simple development model and smooth integration with other components of architecture using out-of-the-box triggers and bindings rank<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_mi_skip_tracking":false},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/matejbobaly.com\/index.php?rest_route=\/wp\/v2\/posts\/5"}],"collection":[{"href":"https:\/\/matejbobaly.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/matejbobaly.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/matejbobaly.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/matejbobaly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5"}],"version-history":[{"count":6,"href":"https:\/\/matejbobaly.com\/index.php?rest_route=\/wp\/v2\/posts\/5\/revisions"}],"predecessor-version":[{"id":87,"href":"https:\/\/matejbobaly.com\/index.php?rest_route=\/wp\/v2\/posts\/5\/revisions\/87"}],"wp:attachment":[{"href":"https:\/\/matejbobaly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/matejbobaly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/matejbobaly.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}