Jekyll2020-06-04T09:55:03+00:00http://hajekj.net/feed/index.xmlHonza’s BlargThings that didn't fit elsewhere...Jan HajekUsing Logic Apps to build simple APIs2020-06-01T07:30:00+00:002020-06-01T07:30:00+00:00http://hajekj.net/2020/06/01/using-logic-apps-to-build-simple-APIs<p><a href="https://azure.microsoft.com/en-us/services/logic-apps/">Logic Apps</a> are Microsoft’s solution for integrations and also a codeless development platform (declarative). If you heard of <a href="http://flow.microsoft.com/">Microsoft Power Automate</a> (formerly known as Microsoft Flow) - which is a citizen developer’s tool to build workflows in a nice visual designer - it runs on top of Logic Apps backend as well.</p>
<p>Thanks to Logic Apps you can easily create event-based (HTTP, Storage, Queue, Microsoft Graph, …) triggered workflows which then perform some actions. In this article, we will focus on those HTTP triggered ones and how-to build a nice and simple API with those.</p>
<p>First off, we start by <a href="https://docs.microsoft.com/en-us/azure/logic-apps/quickstart-create-first-logic-app-workflow">creating a Logic App in Azure Portal</a> (you can also do it in Visual Studio or VS Code). We then start with creating a HTTP trigger (every Logic App needs to have a trigger of some form). Now, when you save the Logic App, the trigger will be something like this (your hostname may be different):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://prod-26.westeurope.logic.azure.com:443/workflows/&lt;id&gt;/triggers/manual/paths/invoke?api-version=2016-10-01&amp;sp=%2Ftriggers%2Fmanual%2Frun&amp;sv=1.0&amp;sig=&lt;signature&gt;
</code></pre></div></div>
<p>And by default, it will accept only HTTP <code class="language-plaintext highlighter-rouge">POST</code> requests. We can modify it to accept <code class="language-plaintext highlighter-rouge">GET</code> or any other HTTP method which we are used to (I switched it to GET for this sample).</p>
<p><img src="/uploads/2020/06/logic-apps-http-trigger-method.jpg" alt="" /></p>
<p>Now obviously, the address above is not really something you want to expose to the public, because of couple of reasons - you probably want to authenticate your API in some nice way, and you also may want to put it to a nicer address like <code class="language-plaintext highlighter-rouge">api.domain.com</code> so that the consumers can work with it nicely.</p>
<p>Here’s where you have few options - you can achieve it <a href="https://docs.microsoft.com/en-us/azure/api-management/import-logic-app-as-api">via API Management</a> (which I will not cover in this article), but you can also achieve it via - wait for it - <a href="https://azure.microsoft.com/en-us/services/functions/">Azure Functions</a>! More specially the <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-proxies">proxies feature</a>.</p>
<p>So in order to get started, we create an Azure Function (I stick with <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#consumption-plan">Consuption plan</a>), create a proxy for our previously created Logic App like so:</p>
<p><img src="/uploads/2020/06/logic-apps-create-functions-proxy.jpg" alt="" /></p>
<p>After that, your Logic App will be reachable at the Function’s address:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://&lt;name&gt;.azurewebsites.net/api/mycustomapiget
</code></pre></div></div>
<p>Next step would be to <a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-custom-domain">map your custom domain</a> and obviously setup HTTPS (preferably with <a href="https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate#create-a-free-certificate-preview">App Service Managed Certificates</a> which are free!).</p>
<p>Alrighty, now we have a Logic App, hidden behind a nice URL with Azure Function Proxies. Above I mentioned something more - authentication. From my past posts, you may know, that I am quite into a feature called <a href="https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization">EasyAuth</a> (App Service Authentication / Authorization), and I am sure you know where I am going with this!</p>
<p>By setting it up, we can protect our Logic App with Azure AD login for example, since Azure Functions will take care of that. Alright, that’s cool, but do we also get all the features of EasyAuth in Logic App? I wouldn’t be writing this if we didn’t, right:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"Cache-Control"</span><span class="p">:</span><span class="w"> </span><span class="s2">"max-age=0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Connection"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Keep-Alive"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Accept"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text/html,application/xhtml+xml,application/xml; q=0.9,image/webp,image/apng,*/*; q=0.8,application/signed-exchange; v=b3; q=0.9"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Accept-Encoding"</span><span class="p">:</span><span class="w"> </span><span class="s2">"br,gzip,deflate"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Accept-Language"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en-US,en; q=0.9,cs; q=0.8,ru; q=0.7"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Cookie"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ARRAffinity=e7b51920af4009b3c02496d96d52092422940c96fcd01492bbb6e49112bf10e4"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prod-26.westeurope.logic.azure.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Max-Forwards"</span><span class="p">:</span><span class="w"> </span><span class="s2">"9"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Referer"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://login.microsoftonline.com/"</span><span class="p">,</span><span class="w">
</span><span class="nl">"User-Agent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Mozilla/5.0,(Windows NT 10.0; Win64; x64),AppleWebKit/537.36,(KHTML, like Gecko),Chrome/84.0.4133.0,Safari/537.36,Edg/84.0.508.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Upgrade-Insecure-Requests"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Sec-Fetch-Site"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cross-site"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Sec-Fetch-Mode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"navigate"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Sec-Fetch-User"</span><span class="p">:</span><span class="w"> </span><span class="s2">"?1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Sec-Fetch-Dest"</span><span class="p">:</span><span class="w"> </span><span class="s2">"document"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-WAWS-Unencoded-URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/api/mycustomapiget"</span><span class="p">,</span><span class="w">
</span><span class="nl">"CLIENT-IP"</span><span class="p">:</span><span class="w"> </span><span class="s2">"10.0.128.22:31811"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-ARR-LOG-ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bd3e3584-8e76-414c-a2f8-8b12ec76523f"</span><span class="p">,</span><span class="w">
</span><span class="nl">"DISGUISED-HOST"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hajekj-logicapps.azurewebsites.net"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-SITE-DEPLOYMENT-ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hajekj-logicapps"</span><span class="p">,</span><span class="w">
</span><span class="nl">"WAS-DEFAULT-HOSTNAME"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hajekj-logicapps.azurewebsites.net"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-Original-URL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/api/mycustomapiget"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-Forwarded-For"</span><span class="p">:</span><span class="w"> </span><span class="s2">"193.86.188.52:56110,193.86.188.52"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-ARR-SSL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2048|256|C=US, S=Washington, L=Redmond, O=Microsoft Corporation, OU=Microsoft IT, CN=Microsoft IT TLS CA 5|CN=*.azurewebsites.net"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-Forwarded-Proto"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-AppService-Proto"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-CLIENT-PRINCIPAL-NAME"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jan.hajek@thenetw.org"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-CLIENT-PRINCIPAL-ID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0336b262-992a-4eaf-87c1-e0b1505cb210"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-CLIENT-PRINCIPAL-IDP"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aad"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-CLIENT-PRINCIPAL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;principal&gt;"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-TOKEN-AAD-ACCESS-TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;access_token&gt;"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-TOKEN-AAD-EXPIRES-ON"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2020-05-10T17:05:01.0000000Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-TOKEN-AAD-REFRESH-TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;refresh_token&gt;"</span><span class="p">,</span><span class="w">
</span><span class="nl">"X-MS-TOKEN-AAD-ID-TOKEN"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;id_token&gt;"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Content-Length"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>This is the raw headers which we received from the Functions proxy into the Logic App - you can see I have my identity, I can even make use of the tokens to call some other service like Microsoft Graph from the Logic App itself!</p>
<p>Just for inspiration, I have already used this to build couple cool production services which I will share more info about soon.</p>
<p>All in all, you should be able to publish your Logic Apps as a public facing, AAD protected API! Stay tuned for more!</p>Jan HajekLogic Apps are Microsoft’s solution for integrations and also a codeless development platform (declarative). If you heard of Microsoft Power Automate (formerly known as Microsoft Flow) - which is a citizen developer’s tool to build workflows in a nice visual designer - it runs on top of Logic Apps backend as well.Generating OpenAPI / Swagger definition from Azure Functions2020-05-11T08:00:00+00:002020-05-11T08:00:00+00:00http://hajekj.net/2020/05/11/generating-openapi-swagger-definition-from-azure-functions<p>When you build a regular web app in ASP.NET Core, you can easily hook <a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-3.1">bunch of tools</a> to it in order to be able to generate OpenAPI definitions. With Azure Functions, this is slightly more complex and challenging.</p>
<p>At the moment there doesn’t appear to be any official release and recommended guidance form Microsoft on how-to generate OpenAPI definitions from Azure Functions. There are many contributions from the community (<a href="https://devkimchi.com/2019/02/02/introducing-swagger-ui-on-azure-functions/">1</a>, <a href="https://medium.com/@yuka1984/open-api-swagger-and-swagger-ui-on-azure-functions-v2-c-a4a460b34b55">2</a>) which make it much easier, but I ended up hitting some incompatibilities between Function runtimes - especially lack of support for V3. Some time ago, I stumbled upon <a href="https://github.com/microsoft/OpenAPI.NET">Microsoft’s OpenAPI implementation</a> which is also used by Swashbuckle for example.</p>
<p>Along with that library, Microsoft has a very nice project called <a href="https://github.com/microsoft/OpenAPI.NET.CSharpAnnotations">OpenAPI.NET.CSharpAnnotations</a> which allows you to generate the definition from <a href="https://docs.microsoft.com/en-us/dotnet/csharp/codedoc">annotation XML</a>. And guess what you can annotate? Yes, Azure Functions as well!</p>
<p>I decided to build a small sample in Azure Functions inspired by the <a href="https://petstore.swagger.io/">Swagger Petstore</a> sample. You can find the <a href="https://github.com/hajekj/azure-functions-openapi-demo">functioning code on my GitHub</a>.</p>
<p>Basically I annotated all the methods and model objects, so that an XML definition gets generated. Along with that, I added a method which generates the OpenAPI document at run-time and serves it to consumers:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"swagger.json"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">RunSwagger</span><span class="p">([</span><span class="nf">HttpTrigger</span><span class="p">(</span><span class="n">AuthorizationLevel</span><span class="p">.</span><span class="n">Anonymous</span><span class="p">,</span> <span class="s">"get"</span><span class="p">,</span> <span class="n">Route</span> <span class="p">=</span> <span class="k">null</span><span class="p">)]</span> <span class="n">HttpRequest</span> <span class="n">req</span><span class="p">,</span> <span class="n">ILogger</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">input</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">OpenApiGeneratorConfig</span><span class="p">(</span>
<span class="n">annotationXmlDocuments</span><span class="p">:</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">XDocument</span><span class="p">&gt;()</span>
<span class="p">{</span>
<span class="n">XDocument</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="s">@"AzureFunctionsOpenAPIDemo.xml"</span><span class="p">),</span>
<span class="p">},</span>
<span class="n">assemblyPaths</span><span class="p">:</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;()</span>
<span class="p">{</span>
<span class="s">@"bin\AzureFunctionsOpenAPIDemo.dll"</span>
<span class="p">},</span>
<span class="n">openApiDocumentVersion</span><span class="p">:</span> <span class="s">"V1"</span><span class="p">,</span>
<span class="n">filterSetVersion</span><span class="p">:</span> <span class="n">FilterSetVersion</span><span class="p">.</span><span class="n">V1</span>
<span class="p">);</span>
<span class="n">input</span><span class="p">.</span><span class="n">OpenApiInfoDescription</span> <span class="p">=</span> <span class="s">"This is a sample description..."</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">generator</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">OpenApiGenerator</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">openApiDocuments</span> <span class="p">=</span> <span class="n">generator</span><span class="p">.</span><span class="nf">GenerateDocuments</span><span class="p">(</span>
<span class="n">openApiGeneratorConfig</span><span class="p">:</span> <span class="n">input</span><span class="p">,</span>
<span class="n">generationDiagnostic</span><span class="p">:</span> <span class="k">out</span> <span class="n">GenerationDiagnostic</span> <span class="n">result</span>
<span class="p">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">HttpResponseMessage</span><span class="p">(</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">OK</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Content</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringContent</span><span class="p">(</span><span class="n">openApiDocuments</span><span class="p">.</span><span class="nf">First</span><span class="p">().</span><span class="n">Value</span><span class="p">.</span><span class="nf">SerializeAsJson</span><span class="p">(</span><span class="n">OpenApiSpecVersion</span><span class="p">.</span><span class="n">OpenApi2_0</span><span class="p">),</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)</span>
<span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You can then use this generated <code class="language-plaintext highlighter-rouge">swagger.json</code> file to consume it in <a href="https://petstore.swagger.io/">Swagger UI</a>, <a href="https://editor.swagger.io/">Swagger Editor</a>, <a href="https://azure.microsoft.com/en-us/services/api-management/">API Management</a> or service of your choice.</p>
<p><img src="/uploads/2020/05/openapi-functions.jpg" alt="Generated definition by Azure Function!" /></p>
<p>So how to get started on an existing project? First off, decide when you want to generate the definition - design-time, build-time or run-time. Obviously design-time doesn’t make much sense (because at desing-time, you make the definition even before you write your code). My sample is going with run-time, but build-time is much more suitable for use with <a href="https://docs.microsoft.com/en-us/azure/api-management/import-and-publish">Azure API Management</a> for example.</p>
<p>Next, enable <em>XML documentation file</em> output in your project, either via GUI or in your <em>.csproj</em> file:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span>
<span class="nt">&lt;TargetFramework&gt;</span>netcoreapp3.1<span class="nt">&lt;/TargetFramework&gt;</span>
<span class="nt">&lt;AzureFunctionsVersion&gt;</span>v3<span class="nt">&lt;/AzureFunctionsVersion&gt;</span>
<span class="nt">&lt;DocumentationFile&gt;</span>AzureFunctionsOpenAPIDemo.xml<span class="nt">&lt;/DocumentationFile&gt;</span>
<span class="nt">&lt;/PropertyGroup&gt;</span>
</code></pre></div></div>
<p>After that a new file should pop in your project folder - make sure to add it to <em>.gitignore</em>, since it is something that is generated and shouldn’t probably be part of your repository (I included it into the repo, so you can take a look at it online).</p>
<p>Then you just either create the endpoint I shown above or generate the definition at build time with this awesome <a href="https://marketplace.visualstudio.com/items?itemName=ms-openapi.OpenApiDocumentTools">DevOps extension</a> provided by Microsoft. It does not only build the definition for you, but it also can publish it into another Git repo seamlessly, so you can run another process from that.</p>
<p>One thing which I miss is having an out-of-box method to convert <code class="language-plaintext highlighter-rouge">IFormCollection</code> to my desired model, but I am sure everyone can come up with some nice way (we use JSON for most of the things anyways in here).</p>
<p>All in all, I see this as the most viable solution for quick use with Azure Functions at the moment, since it is very likely that it will work with Azure Functions vNext and onwards.</p>Jan HajekWhen you build a regular web app in ASP.NET Core, you can easily hook bunch of tools to it in order to be able to generate OpenAPI definitions. With Azure Functions, this is slightly more complex and challenging.Running EasyAuth locally2020-05-01T07:00:00+00:002020-05-01T07:00:00+00:00http://hajekj.net/2020/05/01/running-easyauth-locally<p>Azure App Service has a cool feature which enables your web apps to leverage authentication and authorization without any code changes. It is actually very powerful functionality and in future posts, I will spend some more time digging into it. In this post, I will show you how you can make use of this feature when developing your solutions locally.</p>
<p>This feature is called <em>Authentication / Authorization</em> in the Azure Portal, internally, Microsoft calls it <a href="https://github.com/cgillum/easyauth"><em>EasyAuth</em></a>, and <a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-authentication-how-to">some docs</a> refer to it as <em>AuthN/AuthO</em>. I will stick to Easy Auth.</p>
<p>So why would you want to run it locally in first place? Imagine, you are building an Azure Function for example and you rely on some information parsed from the token by Easy Auth. While you can partially mock it by sending the <a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-authentication-how-to#access-user-claims"><code class="language-plaintext highlighter-rouge">X-MS-*</code></a> headers it can be quite challenging, especially if you need the access token for example.</p>
<p>About a year ago I published an article about <a href="/2019/01/21/exploring-app-service-authentication-on-linux/"><code class="language-plaintext highlighter-rouge">appsvc/middleware</code> Docker image</a>… by now you should probably know where this is heading - we are going to use this official image to run Easy Auth locally in Docker and act as an <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador">ambassador</a> in front of our Function emulator (or whatever else you want to have there).</p>
<p>First off, make sure you have <a href="https://www.hanselman.com/blog/HowToSetUpDockerWithinWindowsSystemForLinuxWSL2OnWindows10.aspx">Docker installed</a> - Scott made a very nice tutorial for it. Also make sure to have <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-local">Azure Functions setup locally</a> (I like using the Visual Studio setup, but obviously, use whatever makes you happy).</p>
<p>We are going to start off by pulling the <a href="https://hub.docker.com/r/appsvc/middleware">image from Docker Hub</a>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull appsvc/middleware:2001061754
</code></pre></div></div>
<p>Once it completes, we need to start the container. In order to do that, we need to provide it proper environment variables. We are going to start by looking at the container logs which actually contain the basic configuration details:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-d</span> <span class="nt">-p</span> 6219:8081 <span class="nt">--name</span> hajekj-asl-auth_0_7acbe16b_middleware <span class="nt">-e</span> <span class="nv">WEBSITE_SITE_NAME</span><span class="o">=</span>hajekj-asl-auth <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_ENABLED</span><span class="o">=</span>True <span class="nt">-e</span> <span class="nv">WEBSITE_ROLE_INSTANCE_ID</span><span class="o">=</span>0 <span class="nt">-e</span> <span class="nv">WEBSITE_HOSTNAME</span><span class="o">=</span>hajekj-asl-auth.azurewebsites.net <span class="nt">-e</span> <span class="nv">WEBSITE_INSTANCE_ID</span><span class="o">=</span>07f9cf5840a21c88ad9faf3878ff016f7bcbae6c77a45c73e38dd7fa16d576c6 <span class="nt">-e</span> <span class="nv">HTTP_LOGGING_ENABLED</span><span class="o">=</span>1 appsvc/middleware:2001061754 /Host.ListenUrl<span class="o">=</span>http://0.0.0.0:8081 /Host.DestinationHostUrl<span class="o">=</span>http://172.16.3.4:8080 /Host.UseFileLogging<span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>
<p>Since it is the real deal which runs in App Service as well, all we need to do is setup a dummy site in App Service (on Linux preferably), configure Easy Auth there and look at the configuration set in Environment Variables. I did this for you, so the entire configuration can be found below:</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">APPDATA</span> <span class="p">=</span> <span class="s">/opt/Kudu/local</span>
<span class="py">APPSETTING_REMOTEDEBUGGINGVERSION</span> <span class="p">=</span> <span class="s">11.0.611103.400</span>
<span class="py">APPSETTING_SCM_USE_LIBGIT2SHARP_REPOSITORY</span> <span class="p">=</span> <span class="s">0</span>
<span class="py">APPSETTING_ScmType</span> <span class="p">=</span> <span class="s">None</span>
<span class="py">APPSETTING_WEBSITE_AUTH_ALLOWED_AUDIENCES</span> <span class="p">=</span> <span class="s">https://hajekj-asl-auth.azurewebsites.net/.auth/login/aad/callback</span>
<span class="py">APPSETTING_WEBSITE_AUTH_AUTO_AAD</span> <span class="p">=</span> <span class="s">False</span>
<span class="py">APPSETTING_WEBSITE_AUTH_CLIENT_ID</span> <span class="p">=</span> <span class="s">b963e9b6-134e-4c2e-a049-4d27fc9b33f3</span>
<span class="py">APPSETTING_WEBSITE_AUTH_CLIENT_SECRET</span> <span class="p">=</span> <span class="s">HIDDEN</span>
<span class="py">APPSETTING_WEBSITE_AUTH_DEFAULT_PROVIDER</span> <span class="p">=</span> <span class="s">AzureActiveDirectory</span>
<span class="py">APPSETTING_WEBSITE_AUTH_ENABLED</span> <span class="p">=</span> <span class="s">True</span>
<span class="py">APPSETTING_WEBSITE_AUTH_LOGOUT_PATH</span> <span class="p">=</span> <span class="s">/.auth/logout</span>
<span class="py">APPSETTING_WEBSITE_AUTH_OPENID_ISSUER</span> <span class="p">=</span> <span class="s">https://sts.windows.net/40c29545-8bca-4f51-8689-48e6819200d2/</span>
<span class="py">APPSETTING_WEBSITE_AUTH_TOKEN_STORE</span> <span class="p">=</span> <span class="s">True</span>
<span class="py">APPSETTING_WEBSITE_AUTH_UNAUTHENTICATED_ACTION</span> <span class="p">=</span> <span class="s">RedirectToLoginPage</span>
<span class="py">APPSETTING_WEBSITE_SITE_NAME</span> <span class="p">=</span> <span class="s">hajekj-asl-auth</span>
<span class="py">APPSVC_RUN_ZIP</span> <span class="p">=</span> <span class="s">FALSE</span>
<span class="py">ASPNETCORE_URLS</span> <span class="p">=</span> <span class="s">http://0.0.0.0:8181</span>
<span class="py">COMPUTERNAME</span> <span class="p">=</span> <span class="s">SmallDedicatedLinuxWebWorkerRoleIN43</span>
<span class="py">DEBIAN_FRONTEND</span> <span class="p">=</span> <span class="s">noninteractive</span>
<span class="py">DOTNET_CLI_TELEMETRY_PROFILE</span> <span class="p">=</span> <span class="s">AzureKudu</span>
<span class="py">DOTNET_RUNNING_IN_CONTAINER</span> <span class="p">=</span> <span class="s">true</span>
<span class="py">DOTNET_SKIP_FIRST_TIME_EXPERIENCE</span> <span class="p">=</span> <span class="s">1</span>
<span class="py">DOTNET_USE_POLLING_FILE_WATCHER</span> <span class="p">=</span> <span class="s">true</span>
<span class="py">ENABLE_ORYX_BUILD</span> <span class="p">=</span> <span class="s">true</span>
<span class="py">FRAMEWORK</span> <span class="p">=</span> <span class="s">PHP</span>
<span class="py">FRAMEWORK_VERSION</span> <span class="p">=</span> <span class="s">7.2</span>
<span class="py">HOME</span> <span class="p">=</span> <span class="s">/home</span>
<span class="py">HOSTNAME</span> <span class="p">=</span> <span class="s">f447f2887d21</span>
<span class="py">HTTP_AUTHORITY</span> <span class="p">=</span> <span class="s">hajekj-asl-auth.scm.azurewebsites.net</span>
<span class="py">HTTP_HOST</span> <span class="p">=</span> <span class="s">hajekj-asl-auth.scm.azurewebsites.net</span>
<span class="py">KUDU_APPPATH</span> <span class="p">=</span> <span class="s">/opt/Kudu</span>
<span class="py">KUDU_RUN_USER</span> <span class="p">=</span> <span class="s">73d289257117fb5c128fd85a</span>
<span class="py">KUDU_WEBSSH_PORT</span> <span class="p">=</span> <span class="s">3000</span>
<span class="py">LANG</span> <span class="p">=</span> <span class="s">C.UTF-8</span>
<span class="py">NOKOGIRI_USE_SYSTEM_LIBRARIES</span> <span class="p">=</span> <span class="s">true</span>
<span class="py">NUGET_PACKAGES</span> <span class="p">=</span> <span class="s">/var/nuget</span>
<span class="py">NUGET_XMLDOC_MODE</span> <span class="p">=</span> <span class="s">skip</span>
<span class="py">OLDPWD</span> <span class="p">=</span> <span class="s">/</span>
<span class="py">ORYX_AI_INSTRUMENTATION_KEY</span> <span class="p">=</span> <span class="s">4aadba6b-30c8-42db-9b93-024d5c62b887</span>
<span class="py">ORYX_ENV_NAME</span> <span class="p">=</span> <span class="s">~1hajekj-asl-auth</span>
<span class="py">ORYX_ENV_TYPE</span> <span class="p">=</span> <span class="s">AppService</span>
<span class="py">PATH</span> <span class="p">=</span> <span class="s">/home/site/deployments/tools:/opt/Kudu/Scripts:/usr/bin:/usr/bin:/usr/local/bin:/usr/local/bin:/usr/local/bin:/usr/local/bin:/usr/local/.rbenv/bin:/usr/local:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/dotnet/sdks/2.2.402:/opt/php/7.2/bin:/opt/oryx:/opt/nodejs/lts/bin:/opt/dotnet/sdks/lts:/opt/python/latest/bin:/opt/yarn/stable/bin:/opt/php/lts/bin:/root/.dotnet/tools:/opt/nodejs/9/bin</span>
<span class="py">PHP_VERSION</span> <span class="p">=</span> <span class="s">7.2</span>
<span class="py">PLATFORM_VERSION</span> <span class="p">=</span> <span class="s">87.0.7.64</span>
<span class="py">PORT</span> <span class="p">=</span> <span class="s">8181</span>
<span class="py">PWD</span> <span class="p">=</span> <span class="s">/opt/Kudu</span>
<span class="py">PYTHONIOENCODING</span> <span class="p">=</span> <span class="s">UTF-8</span>
<span class="py">RBENV_ROOT</span> <span class="p">=</span> <span class="s">/usr/local/.rbenv</span>
<span class="py">REMOTEDEBUGGINGVERSION</span> <span class="p">=</span> <span class="s">11.0.611103.400</span>
<span class="py">RUBY_CFLAGS</span> <span class="p">=</span> <span class="s">-O3</span>
<span class="py">RUBY_CONFIGURE_OPTS</span> <span class="p">=</span> <span class="s">--disable-install-doc</span>
<span class="py">SHLVL</span> <span class="p">=</span> <span class="s">1</span>
<span class="py">SITE_BITNESS</span> <span class="p">=</span> <span class="s">AMD64</span>
<span class="py">ScmType</span> <span class="p">=</span> <span class="s">None</span>
<span class="py">WEBSITE_AUTH_ALLOWED_AUDIENCES</span> <span class="p">=</span> <span class="s">https://hajekj-asl-auth.azurewebsites.net/.auth/login/aad/callback</span>
<span class="py">WEBSITE_AUTH_AUTO_AAD</span> <span class="p">=</span> <span class="s">False</span>
<span class="py">WEBSITE_AUTH_CLIENT_ID</span> <span class="p">=</span> <span class="s">b963e9b6-134e-4c2e-a049-4d27fc9b33f3</span>
<span class="py">WEBSITE_AUTH_CLIENT_SECRET</span> <span class="p">=</span> <span class="s">HIDDEN</span>
<span class="py">WEBSITE_AUTH_DEFAULT_PROVIDER</span> <span class="p">=</span> <span class="s">AzureActiveDirectory</span>
<span class="py">WEBSITE_AUTH_ENABLED</span> <span class="p">=</span> <span class="s">True</span>
<span class="py">WEBSITE_AUTH_ENCRYPTION_KEY</span> <span class="p">=</span> <span class="s">HIDDEN</span>
<span class="py">WEBSITE_AUTH_LOGOUT_PATH</span> <span class="p">=</span> <span class="s">/.auth/logout</span>
<span class="py">WEBSITE_AUTH_OPENID_ISSUER</span> <span class="p">=</span> <span class="s">https://sts.windows.net/40c29545-8bca-4f51-8689-48e6819200d2/</span>
<span class="py">WEBSITE_AUTH_SIGNING_KEY</span> <span class="p">=</span> <span class="s">HIDDEN</span>
<span class="py">WEBSITE_AUTH_TOKEN_STORE</span> <span class="p">=</span> <span class="s">True</span>
<span class="py">WEBSITE_AUTH_UNAUTHENTICATED_ACTION</span> <span class="p">=</span> <span class="s">RedirectToLoginPage</span>
<span class="py">WEBSITE_HOSTNAME</span> <span class="p">=</span> <span class="s">hajekj-asl-auth.azurewebsites.net</span>
<span class="py">WEBSITE_INSTANCE_ID</span> <span class="p">=</span> <span class="s">07f9cf5840a21c88ad9faf3878ff016f7bcbae6c77a45c73e38dd7fa16d576c6</span>
<span class="py">WEBSITE_OWNER_NAME</span> <span class="p">=</span> <span class="s">63dd3a98-6ad8-47ae-ae7f-568e9b3104a8+AppServiceOnLinux-WestEuropewebspace</span>
<span class="py">WEBSITE_PHP_VERSION</span> <span class="p">=</span> <span class="s">7.2</span>
<span class="py">WEBSITE_RESOURCE_GROUP</span> <span class="p">=</span> <span class="s">appserviceonlinux</span>
<span class="py">WEBSITE_ROLE_INSTANCE_ID</span> <span class="p">=</span> <span class="s">0</span>
<span class="py">WEBSITE_SITE_NAME</span> <span class="p">=</span> <span class="s">hajekj-asl-auth</span>
<span class="py">dotnet</span> <span class="p">=</span> <span class="s">/opt/dotnet/sdks/2.2.402/dotnet</span>
<span class="py">php</span> <span class="p">=</span> <span class="s">/opt/php/7.2/bin/php</span>
</code></pre></div></div>
<p>From all these variables, we only need the following:</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">HOME</span> <span class="p">=</span> <span class="s">/home</span>
<span class="py">WEBSITE_AUTH_ALLOWED_AUDIENCES</span> <span class="p">=</span> <span class="s">https://hajekj-asl-auth.azurewebsites.net/.auth/login/aad/callback</span>
<span class="py">WEBSITE_AUTH_AUTO_AAD</span> <span class="p">=</span> <span class="s">False</span>
<span class="py">WEBSITE_AUTH_CLIENT_ID</span> <span class="p">=</span> <span class="s">b963e9b6-134e-4c2e-a049-4d27fc9b33f3</span>
<span class="py">WEBSITE_AUTH_CLIENT_SECRET</span> <span class="p">=</span> <span class="s">HIDDEN</span>
<span class="py">WEBSITE_AUTH_DEFAULT_PROVIDER</span> <span class="p">=</span> <span class="s">AzureActiveDirectory</span>
<span class="py">WEBSITE_AUTH_ENABLED</span> <span class="p">=</span> <span class="s">True</span>
<span class="py">WEBSITE_AUTH_ENCRYPTION_KEY</span> <span class="p">=</span> <span class="s">HIDDEN</span>
<span class="py">WEBSITE_AUTH_LOGOUT_PATH</span> <span class="p">=</span> <span class="s">/.auth/logout</span>
<span class="py">WEBSITE_AUTH_OPENID_ISSUER</span> <span class="p">=</span> <span class="s">https://sts.windows.net/40c29545-8bca-4f51-8689-48e6819200d2/</span>
<span class="py">WEBSITE_AUTH_SIGNING_KEY</span> <span class="p">=</span> <span class="s">HIDDEN</span>
<span class="py">WEBSITE_AUTH_TOKEN_STORE</span> <span class="p">=</span> <span class="s">True</span>
<span class="py">WEBSITE_AUTH_UNAUTHENTICATED_ACTION</span> <span class="p">=</span> <span class="s">RedirectToLoginPage</span>
</code></pre></div></div>
<p>Please note, that the <code class="language-plaintext highlighter-rouge">HIDDEN</code> values need to be properly specified, so either generate them or use the ones generated by App Service for you. Now we need to pass all these variables into the <code class="language-plaintext highlighter-rouge">docker run</code> command in order for the container to start, and also give it the proper <code class="language-plaintext highlighter-rouge">Host.DestinationHostUrl</code> so it knows where to proxy the requests:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-d</span> <span class="nt">-p</span> 8081:8081 <span class="nt">--name</span> middleware <span class="nt">-e</span> <span class="nv">WEBSITE_SITE_NAME</span><span class="o">=</span>localhost <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_ENABLED</span><span class="o">=</span>True <span class="nt">-e</span> <span class="nv">WEBSITE_ROLE_INSTANCE_ID</span><span class="o">=</span>0 <span class="nt">-e</span> <span class="nv">WEBSITE_HOSTNAME</span><span class="o">=</span>localhost <span class="nt">-e</span> <span class="nv">WEBSITE_INSTANCE_ID</span><span class="o">=</span>localhost <span class="nt">-e</span> <span class="nv">HTTP_LOGGING_ENABLED</span><span class="o">=</span>1 <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_ALLOWED_AUDIENCES</span><span class="o">=</span>http://localhost:8081/.auth/login/aad/callback <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_AUTO_AAD</span><span class="o">=</span>False <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_CLIENT_ID</span><span class="o">=</span>b963e9b6-134e-4c2e-a049-4d27fc9b33f3 <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_CLIENT_SECRET</span><span class="o">=</span>HIDDEN <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_DEFAULT_PROVIDER</span><span class="o">=</span>AzureActiveDirectory <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_ENABLED</span><span class="o">=</span>True <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_ENCRYPTION_KEY</span><span class="o">=</span>HIDDEN <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_LOGOUT_PATH</span><span class="o">=</span>/.auth/logout <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_OPENID_ISSUER</span><span class="o">=</span>https://sts.windows.net/40c29545-8bca-4f51-8689-48e6819200d2/ <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_SIGNING_KEY</span><span class="o">=</span>HIDDEN <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_TOKEN_STORE</span><span class="o">=</span>True <span class="nt">-e</span> <span class="nv">WEBSITE_AUTH_UNAUTHENTICATED_ACTION</span><span class="o">=</span>RedirectToLoginPage <span class="nt">-e</span> <span class="nv">HOME</span><span class="o">=</span>/home appsvc/middleware:2001061754 /Host.ListenUrl<span class="o">=</span>http://0.0.0.0:8081 /Host.DestinationHostUrl<span class="o">=</span>http://192.168.1.100:7071 /Host.UseFileLogging<span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>
<p>Make sure to replace the values with your own. Also don’t forget to add the proper Redirect URL to your app’s registration in Azure AD. Next about the <code class="language-plaintext highlighter-rouge">Host.DestinationHostUrl</code>. The URL should be your computer’s IP address along with the appropriate port where the Functions runtime is running - for me it was <code class="language-plaintext highlighter-rouge">192.168.1.100</code> - but this address changes depending on your network.</p>
<p>After starting the container, your site should be available at <code class="language-plaintext highlighter-rouge">http://localhost:8081</code> (you can change this in the command above).</p>
<p><img src="/uploads/2020/05/easyauth-headers-vs.jpg" alt="Headers being passed from Easy Auth to Azure Functions in Visual Studio" /></p>
<p><img src="/uploads/2020/05/easyauth-refresh-me.jpg" alt="Calling me and refresh endpoints via fetch from JavaScript" /></p>
<p>As you can see on the images above, headers are correctly passed to your code and you can even leverage the AJAX endpoints from the browser directly. There you go, a fully working Easy Auth running locally along with Azure Functions or any of your locally running sites!</p>
<h1 id="footnotes">Footnotes</h1>
<p>If you need to access the container’s logs, you can do so by entering it and checking <code class="language-plaintext highlighter-rouge">/var/middleware/logs/middleware.log</code>. The tokens are stored at <code class="language-plaintext highlighter-rouge">/home/data/.auth/tokens/</code>, you can use <a href="https://docs.docker.com/storage/volumes/">volume mounting</a> to access the tokens if needed.</p>
<p>Honestly I am looking forward what else I can make happen with Easy Auth!</p>Jan HajekAzure App Service has a cool feature which enables your web apps to leverage authentication and authorization without any code changes. It is actually very powerful functionality and in future posts, I will spend some more time digging into it. In this post, I will show you how you can make use of this feature when developing your solutions locally.Staticman setup in App Service2020-04-15T16:30:00+00:002020-04-15T16:30:00+00:00http://hajekj.net/2020/04/15/staticman-setup-in-app-service<p>Few months ago, I <a href="/2020/02/04/pardon-the-mess/">wrote a post</a> about moving my blog away from WordPress to GitHub pages. While the general migration to markdown has been quite smooth, setting up the comment system was a pain. Let me share my experience with that!</p>
<p>Since I moved the blog to GitHub, I basically lost the nice comment system in WordPress, combined with <a href="https://jetpack.me">Jetpack</a> and <a href="https://akismet.com/">Akismet</a> for spam protection. Since I chose <a href="https://mmistakes.github.io/minimal-mistakes/">Minimal Mistakes</a> as my theme, it supports multiple commenting engines - Disqus, Discourse, Facebook, utterances and Staticman. Since I was migrating from WordPress and wanted to move the comments as well, I ended up going with Staticman, since it seems to be really simple for setup, all data are stored within the GitHub repository itself.</p>
<p>I could also go with Facebook or some other hosted alternative, but upon some closer look, I figured that migrating existing comments would be rather pain. For comment migration, I used <a href="https://www.npmjs.com/package/wordpress-comments-jekyll-staticman">wp2sm</a> tool, which simply took the comment export from WordPress and generated the files which I then committed.</p>
<p>Staticman is offered in two versions - hosted one (which I started with) and self-hosted one. It’s written in Node.js. Upon setting up Staticman, I set up reCaptcha to prevent spammers from doing their stuff, however it <a href="https://github.com/hajekj/hajekj.github.io/pulls?q=is%3Apr+is%3Aclosed">appears</a> that they found a way around it. To this day, my only clue is that they have someone solving those captchas - which doesn’t really make sense, who would pay someone to spam comments on my blog?</p>
<p>Since reCpatcha wasn’t enough, Staticman also supports <a href="https://akismet.com/">Akismet</a> - yes, the same spam filtering technology which is used by WordPress. Akismet is free for your personal blog, which makes it really attractive. The only issue is, that in order to use Akismet and Staticman, you need to be self-hosting Staticman. There is actually an <a href="https://github.com/eduardoboucas/staticman/pull/195">existing PR</a> for this to be enabled, but the authors of Staticman don’t seem to be too keen to merge it as of now.</p>
<p>So I decided, that the best way would be to run Staticman on my own. By default, Staticman has <a href="https://elements.heroku.com/buttons/eduardoboucas/staticman">one-click deploy to Heroku</a>, but hey! I like Azure, so let’s run it there. Staticman is written in Node.js so the choice of App Service on Linux seems most obvious. So we start with creating our App Service on Linux app in the portal - I went with Node 12 LTS as the runtime image.</p>
<p>Next, we need to deploy the code there - using the <a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-continuous-deployment">Deployment Center</a>, we setup deployment from GitHub - I forked the Staticman repo, and then set is as the source repository. The rest happened in App Service - <a href="https://github.com/microsoft/Oryx/tree/master/doc">Oryx</a> the App Service on Linux build engine picked up the contents, built the app and put it into the storage. Nice!</p>
<p>Now we need to configure the app itself - this was the biggest culprit for me. Unfortunately the <a href="https://staticman.net/docs/api">documentation </a> is slightly outdated, so I ended up browsing the source code and figuring stuff on my own:</p>
<p>All the configuration properties are available in <a href="https://github.com/eduardoboucas/staticman/blob/master/config.js">config.js</a> along with their respective Environment Variable names (App Settings in App Service).</p>
<p>I started with specifying <code class="language-plaintext highlighter-rouge">NODE_ENV</code> to be set to <code class="language-plaintext highlighter-rouge">production</code>, followed by the GitHub configuration. In order to setup GitHub, there are two ways - you can either setup a separate account and get a token for it (that is what their guide recommends), but I didn’t like this approach. My issue with it, is that I don’t want to have another GitHub account, especially when you can make use of <a href="https://developer.github.com/apps/about-apps/">GitHub Apps</a>, which is what the hosted Staticman is using.</p>
<p>First-off, you need to sign up for the <a href="https://developer.github.com/program/">GitHub Developer Program</a> in order to be able to register your application. Assign it following permissions:</p>
<ul>
<li>Contents - Read &amp; write</li>
<li>Metadata - Read-only</li>
<li>Pull Requests - Read &amp; write</li>
</ul>
<p>Next, you <a href="https://github.com/settings/apps">register</a> the application. Once you register it, make note of the <strong>App ID</strong> (not <em>Client ID</em>) and generate a private key at the bottom of the page (you will get a <em>.pem</em> file). The <em>App ID</em> goes to <code class="language-plaintext highlighter-rouge">GITHUB_APP_ID</code> variable, the contents of the certificate (<em>.pem</em> file) go to <code class="language-plaintext highlighter-rouge">GITHUB_PRIVATE_KEY</code> - but again, there’s a catch:</p>
<p>The certificate has multiple lines and when you paste it to the application setting, it will be only a single line, which is perfectly fine. What you however need to do, is to open the <em>Advanced edit</em> and replace the first and last new-line (which got converted to space) with a new-line <code class="language-plaintext highlighter-rouge">\n</code> like so (don’t replace the spaces within the certificate value):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-----BEGIN RSA PRIVATE KEY-----\n&lt;value&gt;\n-----END RSA PRIVATE KEY-----
</code></pre></div></div>
<p>It needs to be done in the <em>Advanced edit</em> of App Settings, otherwise you will end up with an escaped new-line eg. <code class="language-plaintext highlighter-rouge">\\n</code> which won’t work correctly and you will get errors like <code class="language-plaintext highlighter-rouge">[InvalidAsn1Error]: encoding too long</code> or <code class="language-plaintext highlighter-rouge">error:0909006C:PEM routines:get_name:no start line</code>.</p>
<p>The same procedure is for <code class="language-plaintext highlighter-rouge">RSA_PRIVATE_KEY</code> variable which is used for <a href="https://staticman.net/docs/encryption">encrypting secrets</a>. You can generate such key <a href="https://travistidwell.com/jsencrypt/demo/">in your browser</a> or use OpenSSL for example.</p>
<p>The last step is to configure Akismet by setting <code class="language-plaintext highlighter-rouge">AKISMET_SITE</code> which should be the address of your site and <code class="language-plaintext highlighter-rouge">AKISMET_API_KEY</code> which you can get after <a href="https://akismet.com/">signing up</a>.</p>
<p>Then, you have to install your application into your repository by going to your app’s public page (for my app it was: <code class="language-plaintext highlighter-rouge">https://github.com/apps/hajekj-staticman</code>). Last step is to confirm that the app can access the repo, by calling your Staticman instance at <code class="language-plaintext highlighter-rouge">https://hajekj-staticman.azurewebsites.net/v2/connect/hajekj/hajekj.github.io</code> (obviously replace with your site name and user and repo). Once it says <code class="language-plaintext highlighter-rouge">OK!</code> that means you are good to go.</p>
<p>Some of the configuration has to be done in the blog’s repo itself <a href="https://github.com/hajekj/hajekj.github.io/blob/master/staticman.yml"><code class="language-plaintext highlighter-rouge">staticman.yml</code></a> and <a href="https://github.com/hajekj/hajekj.github.io/blob/master/_config.yml"><code class="language-plaintext highlighter-rouge">config.yml</code></a> - refer to <a href="https://mmistakes.github.io/minimal-mistakes/docs/configuration/#static-based-comments-via-staticman">your theme’s docs</a> for more info.</p>
<p>Thanks to hosting it in Azure, you can also <a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/nodejs">enable Application Insights</a> directly from App Service with no code changes required!</p>
<p>After deploying this along with Akismet, I stopped seeing the excessive spam comments (PRs) on my blog!</p>Jan HajekFew months ago, I wrote a post about moving my blog away from WordPress to GitHub pages. While the general migration to markdown has been quite smooth, setting up the comment system was a pain. Let me share my experience with that!Running PHP in Azure Functions2020-04-01T07:00:00+00:002020-04-01T07:00:00+00:00http://hajekj.net/2020/04/01/running-php-in-azure-functions<p>Hi friends! Happy April Fools’ Day! Recently, Microsoft added a new feature to Azure Functions called Custom Handlers. It basically allows you to write Azure Function in any language (kind of reminds me of how <a href="https://dapr.io">Dapr</a> handles communication) and benefit from all the input and output bindings as well. In this article, I will demonstrate how to set it up with PHP.</p>
<p><img src="https://docs.microsoft.com/en-us/azure/azure-functions/media/functions-custom-handlers/azure-functions-custom-handlers-overview.png" alt="Azure Functions Custom Handlers" /></p>
<p>As per the picture, you can see that the Functions Host expects a web server to be running (defined in <code class="language-plaintext highlighter-rouge">host.json</code>) and passes the the request payload.</p>
<p>The setup is going to be fairly simple. In order to run PHP web server, we will use the <a href="https://www.php.net/manual/en/features.commandline.webserver.php">built-in one in PHP</a>. You can start this server locally by just calling <code class="language-plaintext highlighter-rouge">php -S localhost:3000</code> in the directory where your code is, it will serve the requests. With Custom Handlers, the port however is passed via an environment variable <code class="language-plaintext highlighter-rouge">FUNCTIONS_HTTPWORKER_PORT</code>. The issue is, that the PHP server needs the port at startup, so what we need to do is to execute this via either Batch or bash script which will pull the correct port and start the PHP server.</p>
<p>I created a Function app on Windows dedicated App Service Plan. There, I modified the <code class="language-plaintext highlighter-rouge">host.json</code> to execute the desired script:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"httpWorker"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"defaultExecutablePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"phpServer.bat"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"extensionBundle"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Microsoft.Azure.Functions.ExtensionBundle"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[1.*, 2.0.0)"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Then the <code class="language-plaintext highlighter-rouge">phpServer.bat</code> which we need to place into the <code class="language-plaintext highlighter-rouge">wwwroot</code> directory:</p>
<div class="language-bat highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">php</span> <span class="na">-S </span><span class="m">127</span>.0.0.1:<span class="nv">%FUNCTIONS_HTTPWORKER_PORT%</span> <span class="kd">functionRouter</span>.php
</code></pre></div></div>
<p>Note that <code class="language-plaintext highlighter-rouge">127.0.0.1</code> is used instead of <code class="language-plaintext highlighter-rouge">localhost</code>. This is due to PHP’s server binding to specific hostname I suppose. The Functions runtime is calling it via <code class="language-plaintext highlighter-rouge">127.0.0.1</code> so we need to have it there.</p>
<p>Next step is to define the functions. I setup two functions - <code class="language-plaintext highlighter-rouge">function1</code> and <code class="language-plaintext highlighter-rouge">function2</code>. <code class="language-plaintext highlighter-rouge">function1</code> is a simple HTTP in-out function, while <code class="language-plaintext highlighter-rouge">function2</code> is also outputting a message into Azure Storage Queue.</p>
<p><code class="language-plaintext highlighter-rouge">/function1/function.json</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"bindings"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"httpTrigger"</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="s2">"in"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"req"</span><span class="p">,</span><span class="w">
</span><span class="nl">"methods"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"get"</span><span class="p">,</span><span class="w">
</span><span class="s2">"post"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"authLevel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"anonymous"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="s2">"out"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"res"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">/function2/function.json</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"bindings"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"httpTrigger"</span><span class="p">,</span><span class="w">
</span><span class="nl">"authLevel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"function"</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="s2">"in"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"req"</span><span class="p">,</span><span class="w">
</span><span class="nl">"methods"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"post"</span><span class="p">]</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="s2">"out"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"res"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"queue"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"message"</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="s2">"out"</span><span class="p">,</span><span class="w">
</span><span class="nl">"queueName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"orders"</span><span class="p">,</span><span class="w">
</span><span class="nl">"connection"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AzureWebJobsStorage"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The last piece is to write the PHP script to handle this the <code class="language-plaintext highlighter-rouge">functionRouter.php</code>. It is a very simple router, which checks which function to trigger and runs the code. Note that which <code class="language-plaintext highlighter-rouge">function2</code> we output a JSON response, hence the <code class="language-plaintext highlighter-rouge">Content-Type</code> header and we also emit some logs and write to the queue. The logs then appear in Application Insights linked with the function.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>
<span class="k">if</span><span class="p">(</span><span class="nb">strpos</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'REQUEST_URI'</span><span class="p">],</span> <span class="s2">"/function1"</span><span class="p">)</span> <span class="o">!==</span> <span class="kc">FALSE</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s2">"REQUEST_METHOD"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"GET"</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="s2">"World"</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">echo</span> <span class="s2">"Hello "</span><span class="o">.</span><span class="nv">$name</span><span class="o">.</span><span class="s2">" from an Azure Function written in PHP!"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s2">"REQUEST_METHOD"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"POST"</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="s2">"World"</span><span class="p">;</span>
<span class="nv">$json</span> <span class="o">=</span> <span class="nb">file_get_contents</span><span class="p">(</span><span class="s1">'php://input'</span><span class="p">);</span>
<span class="nv">$jsonBody</span> <span class="o">=</span> <span class="nb">json_decode</span><span class="p">(</span><span class="nv">$json</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$jsonBody</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="nv">$jsonBody</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">echo</span> <span class="s2">"Hello "</span><span class="o">.</span><span class="nv">$name</span><span class="o">.</span><span class="s2">" from an Azure Function written in PHP!"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="nb">strpos</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'REQUEST_URI'</span><span class="p">],</span> <span class="s2">"/function2"</span><span class="p">)</span> <span class="o">!==</span> <span class="kc">FALSE</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">header</span><span class="p">(</span><span class="s2">"Content-Type: application/json"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s2">"REQUEST_METHOD"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"POST"</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="s2">"World"</span><span class="p">;</span>
<span class="nv">$json</span> <span class="o">=</span> <span class="nb">file_get_contents</span><span class="p">(</span><span class="s1">'php://input'</span><span class="p">);</span>
<span class="nv">$jsonBody</span> <span class="o">=</span> <span class="nb">json_decode</span><span class="p">(</span><span class="nv">$json</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$jsonBody</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="nv">$jsonBody</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span>
<span class="p">}</span>
<span class="nv">$message</span> <span class="o">=</span> <span class="s2">"Hello "</span><span class="o">.</span><span class="nv">$name</span><span class="o">.</span><span class="s2">" from an Azure Function written in PHP!"</span><span class="p">;</span>
<span class="k">echo</span> <span class="nb">json_encode</span><span class="p">([</span>
<span class="s2">"Outputs"</span> <span class="o">=&gt;</span> <span class="p">[</span>
<span class="s2">"message"</span> <span class="o">=&gt;</span> <span class="nv">$message</span><span class="p">,</span>
<span class="s2">"res"</span> <span class="o">=&gt;</span> <span class="p">[</span>
<span class="s2">"statusCode"</span> <span class="o">=&gt;</span> <span class="mi">200</span><span class="p">,</span>
<span class="s2">"body"</span> <span class="o">=&gt;</span> <span class="nv">$message</span>
<span class="p">]</span>
<span class="p">],</span>
<span class="s2">"Logs"</span> <span class="o">=&gt;</span> <span class="p">[</span>
<span class="s2">"Request completed"</span>
<span class="p">]</span>
<span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
<span class="nb">phpinfo</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>So as you can see, the Custom Handlers in Azure Functions can pretty much run any language, including your PHP code and you can easily integrate it with <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings">Azure Functions Bindings</a>. Two more things to note:</p>
<ul>
<li>The default PHP version on the Windows worker is 5.6, which you probably want to change. If you want to make such a change, you can use either the <code class="language-plaintext highlighter-rouge">az</code> CLI or <a href="https://resources.azure.com/">Resource Explorer</a> - you need to navigate to <code class="language-plaintext highlighter-rouge">&lt;yoursitename&gt;/config/web</code> and change the <code class="language-plaintext highlighter-rouge">phpVersion</code> property to for example <code class="language-plaintext highlighter-rouge">7.3</code>.</li>
<li>I wouldn’t really use this in production, since the built-in PHP webserver is only for testing, and not really production workloads.</li>
</ul>Jan HajekHi friends! Happy April Fools’ Day! Recently, Microsoft added a new feature to Azure Functions called Custom Handlers. It basically allows you to write Azure Function in any language (kind of reminds me of how Dapr handles communication) and benefit from all the input and output bindings as well. In this article, I will demonstrate how to set it up with PHP.Microsoft.Identity.Web Sneak Peek2020-03-25T07:00:00+00:002020-03-25T07:00:00+00:00http://hajekj.net/2020/03/25/microsoft-identity-web-sneak-peek<p>About a year ago, I posted about <a href="/2018/12/17/microsoft-authentication-graph-helpers/">Microsoft Authentication Graph Helpers</a>. While it set an example on how to standardize identity setup in ASP.NET Core projects, Microsoft has done a way better job - by creating <a href="https://github.com/AzureAD/microsoft-identity-web">Microsoft.Identity.Web</a>.</p>
<p>Microsoft.Identity.Web has been around for quite a while (roughly 7 months at the time of writing) as part of <a href="https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/Microsoft.Identity.Web">samples</a>. Recently, it has been moved to a <a href="https://github.com/AzureAD/microsoft-identity-web">separate repository</a> and the commit messages in the sample actually <a href="https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/commit/d5166bb96f7e8f961fcc84fd9ce9c202117e2545">hint</a> that it is soon to be released as a NuGet package which we all can make use of easily.</p>
<p>Generally, it focuses on the following issues:</p>
<ul>
<li>Easily add authentication via Azure AD or B2C to your application</li>
<li>Easily protect your API with Azure AD or B2C</li>
<li>Streamlined token caching (includes in-memory, session and distributed memory cache implementations which is super cool)</li>
<li>Fully built on top of MSAL</li>
<li>They include scope based authentication attributes which can be used in controllers</li>
<li>Shortcuts for calling the On-Behalf-Of flow</li>
<li>Support for conditional access from down stream APIs (yay, this is super cool too!)</li>
</ul>
<p>What I love about this the most is that it covers most of the scenarios which you can encounter with Azure AD authentication in your web app.</p>
<p>Once it gets released as NuGet, I plan to port some of the functionality which I had in the Graph Helpers (the <code class="language-plaintext highlighter-rouge">AzureAdAuthorization</code> attribute for group-based/role-based authentication and <code class="language-plaintext highlighter-rouge">MicrosoftGraphFactory</code>) and try to have it as an extension to the Microsoft’s package, since for example in our apps, we use group-based authorization quite heavily.</p>
<p>I really can’t wait to see how this will evolve, especially if there are any plans to make the entire flow more friendly with Azure Functions.</p>
<p>Once Microsoft releases it as a NuGet package (v1), I plan to cover this library more deeply and post some experiences I had with moving existing applications to it.</p>Jan HajekAbout a year ago, I posted about Microsoft Authentication Graph Helpers. While it set an example on how to standardize identity setup in ASP.NET Core projects, Microsoft has done a way better job - by creating Microsoft.Identity.Web.Azure Functions and Azure Files2020-02-14T07:00:00+00:002020-02-14T07:00:00+00:00http://hajekj.net/2020/02/14/azure-functions-and-azure-files<p>Lately, I have been playing with Azure Functions. Since the release of v3 runtime, I noticed a really cool thing which signifies a nice progress in overall App Service architecture as well.</p>
<p>If we go back few years, I have been blogging about <a href="https://hajekj.net/2016/11/14/speed-up-your-application-in-azure-app-service/">Speeding up your application in App Service</a>. The article provided some insight into why some applications, especially those running on runtimes using JIT compilation (like PHP) might be slow and what can be done about it. Tldr; the reason was primarily the speed of storage which proved to be a real bottleneck.</p>
<p>In the past days I had to troubleshoot some Functions triggers and while browsing through the storage, I noticed something which I wasn’t expecting. For the Windows consumption plan - you can notice that a file share is created in the storage account associated with the function:</p>
<p><img src="/uploads/2020/02/azure-functions-files-structure.jpg" alt="Azure Functions Storage" /></p>
<p>This is quite a significant progress as Azure Files provide decent performance and capacity and are scalable.</p>
<p>Obviously, I wanted to learn more about how it works behind the scenes… Azure Functions are running on top of <a href="https://github.com/Azure/azure-webjobs-sdk">Azure Web Jobs</a>. Functions leverage that SDK and have their of <a href="https://github.com/Azure/azure-functions-host">Functions Host</a>. The host allows for your code to run within an environment where it is setup like Azure Functions, Azure Stack or even on-prem or 3rd party cloud via <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-kubernetes-keda">KEDA</a>.</p>
<p>Since the host is open-source and available on GitHub, I decided to take a <a href="https://github.com/Azure/azure-functions-host/search?p=1&amp;q=FileShare&amp;unscoped_q=FileShare">peek</a> (gotta love reading through GitHub, I really hope they enable <a href="https://help.github.com/en/github/managing-files-in-a-repository/navigating-code-on-github">code navigation</a> for C# soon). The Functions host is really interesting piece of code, especially if you want to learn more about how it works - like dynamic extension downloading when using <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2016/january/essential-net-csharp-scripting">C# script</a> (.csx), bindings and much more.</p>
<p>Going through the code, you can easily bump into <code class="language-plaintext highlighter-rouge">MeshServiceClient</code> (<a href="https://github.com/Azure/azure-functions-host/blob/d0410b6dd1c032765a53bc931330cc989b7edaaf/src/WebJobs.Script.WebHost/Management/MeshServiceClient.cs">source</a>) which exposes couple interesting methods - <code class="language-plaintext highlighter-rouge">MountCifs</code>, <code class="language-plaintext highlighter-rouge">MountBlob</code> and <code class="language-plaintext highlighter-rouge">MountFuse</code>. After further reading you can learn the following:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">MountCifs</code> is used for mounting the Azure File Share associated with the function</li>
<li><code class="language-plaintext highlighter-rouge">MountBlob</code> is used when blob storage is used for storage</li>
<li><code class="language-plaintext highlighter-rouge">MountFuse</code> is used for <a href="https://docs.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package">running from ZIP</a>. It also appears to <a href="https://github.com/Azure/azure-functions-host/blob/bd350f348fd48c5ba12d0b540db45003dc915da2/src/WebJobs.Script.WebHost/Management/InstanceManager.cs#L440">include support</a> for <a href="https://en.wikipedia.org/wiki/SquashFS">SquashFS</a> which is a compression read-only filesystem for Linux (I haven’t heard about it until now)</li>
</ul>
<p>This generally means that the Function storage is no longer using the regular App Service storage which was slowing things down. And this may also have some good impact on cold starts as well.</p>
<p>One more interesting thing which caught my eyes was <code class="language-plaintext highlighter-rouge">MeshServiceClient</code> itself. Ever heard about <a href="https://docs.microsoft.com/en-us/azure/service-fabric-mesh/service-fabric-mesh-overview">Service Fabric Mesh</a>? It is basically hyper scalable serverless platform for running microservices (probably more about that in another article). That means that Functions are probably moving away from App Service infrastructure into a more modern and scalable one!</p>
<p>The only thing which bothers me right now is that the File Shares are only mounted for Windows Consumption plans. I would really love to see the File Shares used more with App Service to see how much of a performance boost it can bring.</p>
<p>Also, just a side-note, with App Service on Linux you can already make use of mounted Azure Files / mounted blob storage (<a href="https://docs.microsoft.com/en-us/azure/app-service/containers/how-to-serve-content-from-azure-storage">docs</a>). I will test the performance in future article.</p>
<p>Until next time…</p>Jan HajekLately, I have been playing with Azure Functions. Since the release of v3 runtime, I noticed a really cool thing which signifies a nice progress in overall App Service architecture as well.Pardon the mess…2020-02-04T07:00:00+00:002020-02-04T07:00:00+00:00http://hajekj.net/2020/02/04/pardon-the-mess<p>Hi everyone! Long time no hear!</p>
<p>I got this great idea to migrate my blog from WordPress into <a href="https://pages.github.com/">GitHub Pages</a>! I have been running WordPress for quite a while now - switched some hostings and ended up running on <a href="https://github.com/hajekj/hajekjnet-php/">my own VM in Azure</a>.</p>
<!--more-->
<p>I am still polishing some things, hopefully everything will be working. I tried to make sure that the <a href="/feed">RSS feed</a> remains available under the same address, and hopefully all the posts.</p>
<p>Some technical reasoning is - due to some unknown reasons, the underlying database of WordPress became corrupt and therefore I managed to loose couple posts in progress and had to restore them from backup. After that, I decided to just go with the static generated site which can be moved and built anywhere - may it be GitHub or Azure DevOps in future.</p>
<p>I will be actually doing the same for <a href="https://blog.thenetw.org">NETWORG’s blog</a> in near future. However, I have to also express my gratitude to the great WordPress community and WordPress on its own, which is constantly amazing me. If you want to hear some cool stories I suggest that you read this book - <a href="https://scottberkun.com/yearwithoutpants/">The Year Without Pants</a>.</p>
<p>Also, I will be enabling comments on the blog shortly again.</p>
<p>I originally wanted to flatten the initial changes I was making to the posts, but decided to keep them in the history, in case something got lost. You can find the source of this blog <a href="https://github.com/hajekj/hajekj.github.io">here</a>.</p>Jan HajekHi everyone! Long time no hear!Fixing WinSCP editor error2019-06-10T07:00:56+00:002019-06-10T07:00:56+00:00http://hajekj.net/2019/06/10/fixing-winscp-editor-error<p>I have been a heavy user of <a href="https://winscp.net">WinSCP</a> for quite a few years. It is a great tool whether you need to connect to FTP, SFTP or even WebDAV servers. WinSCP offers an integration with code editors so that you can open the remote file and edit it with your local editor. Since April, I started hitting a <a href="https://winscp.net/forum/viewtopic.php?t=27695">weird issue</a> which prevented me from using this feature correctly.</p>
<!--more-->
<p>For a while WinSCP has been offering an option to be installed via <a href="https://www.microsoft.com/store/apps/9p0pq8b65n8x?cid=downloads">Microsoft Store</a> which is really awesome - automated background updates!</p>
<p>Basically the application has been converted into MSIX package and distributed through Microsoft Store. I have been using the editor feature for few months successfuly but suddenly, I started seeing following errors:</p>
<pre class="wp-block-preformatted"><em>System Error. Code: 1392.
The file or directory is corrupted and unreadable</em> </pre>
<p>During that, the only workaround was to launch it via shortcut ( <em>C:\Users\hajek\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd</em>) through command-line which worked, but I sometimes ended up with multiple command-line windows open.</p>
<p>Since I don't really edit code this way anymore, rather server configuration from time to time or use it for file transfer only, I didn't pay much attention.</p>
<p>Today, I decided to figure out what was going on. After reading some stuff about how <a href="https://docs.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes#file-system">File System operations</a> work in packaged apps, I reallized that the issue must there.</p>
<p>So I browsed through the app's filesystem which is located at: <em>C:\Users\%USERNAME%\AppData\Local\Packages\MartinPikryl.WinSCP_tvv458r3h9r5m\</em> and found what I was looking for!</p>
<p>The <em>Code.exe</em> file was present there (in <em>LocalCache\Local\Programs\Microsoft VS Code</em>) in some weird corrupt state. I simply went and deleted the VS Code folder and everything started to work like before!</p>Jan HajekI have been a heavy user of WinSCP for quite a few years. It is a great tool whether you need to connect to FTP, SFTP or even WebDAV servers. WinSCP offers an integration with code editors so that you can open the remote file and edit it with your local editor. Since April, I started hitting a weird issue which prevented me from using this feature correctly.Protip: Transfer Chrome cookies to Microsoft Edge2019-04-23T07:00:27+00:002019-04-23T07:00:27+00:00http://hajekj.net/2019/04/23/protip-transfer-chrome-cookies-to-microsoft-edge<p>As you might have heard, Microsoft has released a preview version of Microsoft Edge powered by Chromium. If you were using Chrome before and want to make a seamless switch without having to re-log to all your favorite sites, read on!</p>
<!--more-->
<p>First off, you need to locate your Chrome's app data, which are located over here<em>C:\Users\hajek\AppData\Local\Google\Chrome\User Data\Default</em>. You can just use the line below to open the same location in your Explorer:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%LOCALAPPDATA%\Google\Chrome\User Data\Default
</code></pre></div></div>
<p>In there, you want to find a file called <code class="language-plaintext highlighter-rouge">Cookies</code>. Now you need to copy and paste that file to the Edge app data, which are located at <code class="language-plaintext highlighter-rouge">C:\Users\hajek\AppData\Local\Microsoft\Edge SxS\User Data\Default</code> for me (Edge Canary) or <code class="language-plaintext highlighter-rouge">Dev</code> or <code class="language-plaintext highlighter-rouge">Beta</code>, depending which channel you are on:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%LOCALAPPDATA%\Microsoft\Edge SxS\User Data\Default
</code></pre></div></div>
<p>Same works for other profiles you may have in the browser - located at <code class="language-plaintext highlighter-rouge">..\Google\Chrome\User Data\Profile X\</code> where X is the number of the profile.</p>
<p>This should make your switch easier. I strongly suggest however copying only the <em>Cookies</em> file and leaving the rest to the import process of Edge, because some of the data may result in loss of functionality, corruption etc.</p>Jan HajekAs you might have heard, Microsoft has released a preview version of Microsoft Edge powered by Chromium. If you were using Chrome before and want to make a seamless switch without having to re-log to all your favorite sites, read on!