Ideas Of Anders ÅbergJekyll2022-01-19T08:43:33-05:00https://ideasof.andersaberg.com/Anders Åberghttps://ideasof.andersaberg.com/anders@andersaberg.com
https://ideasof.andersaberg.com/development/guide-setup-grafana-agent-collect-logs
https://ideasof.andersaberg.com/development/guide-setup-grafana-agent-collect-logs2022-01-19T00:00:00-05:002022-01-19T00:00:00-05:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>This is a beginners guide on how to collect log files on your linux machine using Grafana Agent and publish them to Grafana Cloud.
<em>Disclaimer: I conside myself a beginner in both linux, docker, grafana and yaml - this guide is written for beginners like me.</em></p>
<p>There are multiple ways of installing Grafana Agent. I prefer to run Grafana Agent inside an docker container.</p>
<h1 id="pre-install-docker">Pre: Install docker</h1>
<p>First, install docker on your linux machine.
There are multiple ways of doing this, depeneding on your OS.
I’m using AMI2 (Amazon EC2 default OS):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install docker
sudo service docker start
sudo usermod -a -G docker ec2-user
</code></pre></div></div>
<p>This script installs docker, make sure docker starts on reboot and enables the <code class="language-plaintext highlighter-rouge">ec2-user</code> to run <code class="language-plaintext highlighter-rouge">docker</code>-commands without <code class="language-plaintext highlighter-rouge">sudo</code>.</p>
<h1 id="create-your-grafana-agent-config">Create your grafana-agent config.</h1>
<p>The config instructs the grafana agent what files to collect and how to upload them to Grafana Cloud.
You can create a boilerplate config in Grafana Cloud by doing the <a href="https://your-grafana.grafana.net/a/grafana-easystart-app/hlInstanceId">onboarding</a> and picking Loki.</p>
<p>It will setup an API key automatically. However, creating an API key can be done separately.</p>
<p>Your <code class="language-plaintext highlighter-rouge">grafana-config.yaml</code> should look something like this.
You can save this file anywhere, I keep it in <code class="language-plaintext highlighter-rouge">~/grafana-config.yaml</code>.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">logs</span><span class="pi">:</span>
<span class="na">configs</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Anders</span><span class="nv"> </span><span class="s">Grafana</span><span class="nv"> </span><span class="s">Cloud"</span>
<span class="na">positions</span><span class="pi">:</span>
<span class="na">filename</span><span class="pi">:</span> <span class="s">/var/log/anders/positions.yaml</span> <span class="c1">#This file needs to persisted to the host using a `-v volume` or logs might be re-read. </span>
<span class="na">clients</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">url</span><span class="pi">:</span> <span class="s">https://89231:ey....HaYuhsw70OH0=@logs-prod-us-central1.grafana.net/loki/api/v1/push</span>
<span class="na">scrape_configs</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s">anderslogs</span>
<span class="na">static_configs</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">localhost</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">job</span><span class="pi">:</span> <span class="s">anderslogs</span>
<span class="na">host</span><span class="pi">:</span> <span class="s2">"</span><span class="s">anders_vm1"</span>
<span class="na">__path__</span><span class="pi">:</span> <span class="s">/var/log/anders/**/*.log</span>
</code></pre></div></div>
<p>There are many more options available, read about them in the <a href="https://grafana.com/docs/agent/latest/configuration/logs-config/">Grafana Agent documentation</a>.</p>
<h1 id="start-the-grafana-agent-in-docker">Start the grafana agent in docker</h1>
<p>We will now launch the grafana agent as an docker container.
However, since the agent is running inside a contianer it <em>only</em> has access to read files that exists inside that container. It won’t have access the files on your machine that it needs:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">/home/ec2-user/grafana-config.yaml</code> - read config</li>
<li><code class="language-plaintext highlighter-rouge">/var/log/anders/positions.yaml</code> - keep track of what has already been read from files</li>
<li><code class="language-plaintext highlighter-rouge">/var/log/anders/*.log</code> - the log-files that will be read</li>
</ul>
<p>Let’s fix that by using volumes in docker.
When you run a docker container, you can map files and folders on the host system to be part of the docker containers filesysem.
This is done using <a href="https://docs.docker.com/engine/reference/commandline/run/#mount-volume--v---read-only">docker volumes</a> and the <code class="language-plaintext highlighter-rouge">-v</code> or <code class="language-plaintext highlighter-rouge">--read-only</code> arguments to <code class="language-plaintext highlighter-rouge">docker run</code>.</p>
<p>For each directory we want to make available to docker, we add mount. E.g. <code class="language-plaintext highlighter-rouge">-v /home/ec2-user/grafana-config.yaml:/etc/agent/agent.yaml</code>.</p>
<p>Note that the grafana-agent needs to save some temporary and operational data on the host, so we make they are mounted:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">-v /tmp/agent:/etc/agent/data</code> - store temporary data</li>
<li><code class="language-plaintext highlighter-rouge">-v /home/ec2-user/grafana-config.yaml:/etc/agent/agent.yaml</code> - Config</li>
<li><code class="language-plaintext highlighter-rouge">-v /home/ec2-user/positions.yaml:/var/log/anders/positions.yaml</code> - Positions file</li>
</ul>
<p>With mappings, our start command looks like this:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="se">\</span>
<span class="nt">-v</span> /tmp/agent:/etc/agent/data <span class="se">\</span>
<span class="nt">-v</span> /home/ec2-user/grafana-config.yaml:/etc/agent/agent.yaml <span class="se">\</span>
<span class="nt">-v</span> /var/log/anders:/var/log/anders <span class="se">\</span>
grafana/agent:v0.22.0
</code></pre></div></div>
<p>Tip: Since our Grafana Agent config collects all log-files (including sub directories) in /var/log/anders/*, we can simply add mounts to subfolders and have new log files automatically picked up by grafana:
<code class="language-plaintext highlighter-rouge">-v /home/ec2-user/my_logs/:/var/log/anders/my_logs</code>.</p>
<p><strong>Write your logs to the right place</strong><br />
I either write logs from my apps to <code class="language-plaintext highlighter-rouge">/var/log/anders/something.log</code> to have it automatically collected by Grafana Agent.
If the logs are written somewhere else, I add a mount to a subfolder, e.g. <code class="language-plaintext highlighter-rouge">/var/log/anders/something/</code> and Grafana Agent will automatically collect it.</p>
<p><strong>Your logs should now be available in your Grafana Cloud.</strong><br />
Open Grafana Cloud > Explore. Click on Log Browser to see what labels/files have been collected.</p>
<h4 id="always-run-grafana-agent-automatically-starts-on-reboots-background">Always run Grafana Agent (automatically starts on reboots, background)</h4>
<p>I put my start command in a script and extended it with a name and dockers built in way to keep a container alive:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="c"># Removes existing container with this name so that we can start a new</span>
docker container stop grafana-agent &> /dev/null
docker container <span class="nb">rm </span>grafana-agent &> /dev/null
<span class="c"># Use -v to map a file/folder to /var/log/anders to have it automatically picked up by Grafana Agent</span>
<span class="c"># -d, start the container in the background </span>
<span class="c"># --name is optional, but helps to make sure only one agent is running at once.</span>
<span class="c"># --restart unless-stopped will automatically start your container on crashes/reboot etc.</span>
docker run <span class="se">\</span>
<span class="nt">-d</span> <span class="se">\</span>
<span class="nt">--name</span> grafana-agent <span class="se">\</span>
<span class="nt">--restart</span> unless-stopped <span class="se">\</span>
<span class="nt">-v</span> /tmp/agent:/etc/agent/data <span class="se">\</span>
<span class="nt">-v</span> /home/ec2-user/grafana-config.yaml:/etc/agent/agent.yaml <span class="se">\</span>
<span class="nt">-v</span> /var/log/anders:/var/log/anders <span class="se">\</span>
<span class="nt">-v</span> /home/ec2-user/logs:/var/log/anders/user <span class="se">\</span>
grafana/agent:v0.22.0
</code></pre></div></div>
<p>Run the script everytime you change volume mounts or the config to restart the Grafana Agent.</p>
<p><strong>Tip:</strong> If you have a problem beeing duplicated upon restarts, make sure the <code class="language-plaintext highlighter-rouge">position</code>-path in your <code class="language-plaintext highlighter-rouge">grafana-config.yaml</code> is persisted to the host machine using a volume mount.</p>
<p><a href="https://ideasof.andersaberg.com/development/guide-setup-grafana-agent-collect-logs">Guide - Collect logs using Grafana Agent (Loki)</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on January 19, 2022.</p>
https://ideasof.andersaberg.com/development/grafana-for-slack
https://ideasof.andersaberg.com/development/grafana-for-slack2021-08-30T00:00:00-04:002021-08-30T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p><a href="https://www.grafanaforslack.com">Grafana for slack</a> is an easy way to share your grafana panels to slack. It’s a “scratch my own itch” solution, I wanted to make it easy to share a panel on a cron schedule, e.g. every morning to a specific slack channel. It’s well integrated with Slack and only requires your Grafana API key and link to grafana panel. It alsow works well with Grafana Cloud. Note however that it relies on you having the Grafana Image renderer plugin installed, which is available for free.</p>
<p>It supports cron syntax, multiple channels and even a <code class="language-plaintext highlighter-rouge">/grafana</code> slack slash command.
You can send a panel on demand using <code class="language-plaintext highlighter-rouge">/grafana title</code> - as long as you’ve added it to <a href="https://www.grafanaforslack.com">Grafana for slack</a> before.</p>
<p><a href="https://ideasof.andersaberg.com/development/grafana-for-slack">Introducing: Grafana for slack</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on August 30, 2021.</p>
https://ideasof.andersaberg.com/development/passwordless-api
https://ideasof.andersaberg.com/development/passwordless-api2020-11-23T00:00:00-05:002020-11-23T00:00:00-05:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<h1 id="sign-in-with-fingerprint---aspnet">Sign in with fingerprint - asp.net</h1>
<p>Using the <a href="https://beta.passwordless.dev/">Passwordless API</a> we can add secure and fast sign in to our existing asp.net apps. The API allows us to add sign in with FaceID, Fingerprint scan or other native methods like the android lock screen or Windows Hello. It makes it easy to consume the complex browser standards WebAuthn and FIDO2.</p>
<p>Example on iOS 14:<br />
<img src="https://user-images.githubusercontent.com/357283/100014365-8e627300-2dd6-11eb-8e3d-db7566adfacb.png" alt="Sign in with FaceID - asp.net" /></p>
<p>Exampe of Windows 10:<br />
<img src="https://user-images.githubusercontent.com/357283/100014246-696e0000-2dd6-11eb-93c0-996ab963df42.png" alt="Sign in with Windows Hello - asp.net" /></p>
<h2 id="who-is-this-guide-for">Who is this guide for?</h2>
<p>It’s for you, a asp.net developer who want to make your existing web app <strong>more secure</strong> and <strong>faster</strong> to sign in to. I assume you already have an existing user database, probably using username / password.</p>
<p>During this blog post we will implement passwordless sign in from start to finish, using the passwordless api.</p>
<h2 id="overview">Overview</h2>
<p>There are a couple of steps in implementing this. We will need to:</p>
<ol>
<li>Get a free API key</li>
<li>Allow a existing user to enable passwordless sign in</li>
<li>Allow a user to sign in without a password</li>
</ol>
<p>Both steps require both some client side code and some backend code, but it’s not much.</p>
<h2 id="getting-your-passwordless-api-key">Getting your Passwordless API key</h2>
<p>Start by <a href="https://beta.passwordless.dev/create-account">getting your free api key</a>. The free plan is perfect for getting started. There are paid plans for when you want to take things to the next level, but the free plan works well as a starting point.</p>
<p><strong>Make sure you copy these keys to a safe place, they can not be retrieved ever again.</strong>. If you loose your API keys, you will need to create a new account.</p>
<h2 id="allow-existing-users-to-enable-passwordless-sign-in">Allow existing users to enable passwordless sign in</h2>
<p>Anywhere in your app, you want to add a button that allows a user to enable passwordless sign in. Perhaps you place this button under Account Settings. This could also be done during sign up, it’s up to you.</p>
<p>You need to add both some backend code and some client side code, let’s start with the client side:</p>
<p>We will add a button and include the passwordless client library.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script </span><span class="na">src=</span><span class="s">"https://cdn.passwordless.dev/dist/0.0.1/passwordlessclient.min.js"</span> <span class="na">integrity=</span><span class="s">"sha384-TPor6eIWM4IefSReNrio8zR0tr3LIHYNSwlSNKArZo42TEWTmByjkkJm/vvnUxxv"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">></script></span>
<span class="nt"><button</span> <span class="na">id=</span><span class="s">"passwordless-enable"</span><span class="nt">></span>Enable passswordless sign in<span class="nt"></button></span>
<span class="nt"><script></span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">RegisterPasswordless</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">({</span>
<span class="na">apiKey</span><span class="p">:</span> <span class="dl">"</span><span class="s2">demo:public:xxx</span><span class="dl">"</span> <span class="c1">// replace with your public api key</span>
<span class="p">});</span>
<span class="c1">// call your backend to get a passwordless token</span>
<span class="kd">var</span> <span class="nx">myToken</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">/example-backend/passwordless/token</span><span class="dl">"</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=></span> <span class="nx">r</span><span class="p">.</span><span class="nx">text</span><span class="p">());</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">p</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="nx">myToken</span><span class="p">);</span>
<span class="c1">// if no error is returned, the credential was successfully registered</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Finished registering, you can now sign in!</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Things went bad: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">passwordless-enable</span><span class="dl">'</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="nx">RegisterPasswordless</span><span class="p">);</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>New let’s add some code to your backend to get that token.
It will be easier if we install the .net <a href="https://www.nuget.org/packages/PasswordlessClient/">PasswordlessClient</a> via nuget:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Install-Package PasswordlessClient</code></li>
<li>or <code class="language-plaintext highlighter-rouge">dotnet add package PasswordlessClient</code></li>
</ul>
<p>If you do not install the .net PasswordlessClient library, you can do the Rest API call <a href="https://github.com/passwordless/passwordless-client-js#register-a-webauthn-credential-to-user">manually</a>.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// File: AccountController.cs (or other suitable controller)</span>
<span class="c1">// we're using GET for simplicity, for security reasons you might want to use POST and send a xsrf-token.</span>
<span class="p">[</span><span class="n">HttpGet</span><span class="p">]</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">GetPasswordlesToken</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// get the currently signed in user, perhaps via cookie or HttpContext.</span>
<span class="kt">var</span> <span class="n">username</span> <span class="p">=</span> <span class="n">Request</span><span class="p">.</span><span class="n">Cookies</span><span class="p">[</span><span class="s">"User"</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">apiSecret</span> <span class="p">=</span> <span class="s">"demo:secret:yyy"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">();</span>
<span class="c1">// get a token that allows registering a passwordless credential for this username</span>
<span class="kt">var</span> <span class="n">token</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="nf">GetPasswordlessRegisterToken</span><span class="p">(</span><span class="k">new</span> <span class="nf">PasswordlessTokenParameters</span><span class="p">(</span><span class="n">apiSecret</span><span class="p">,</span> <span class="n">username</span><span class="p">));</span>
<span class="c1">// return the token to the client side code</span>
<span class="k">return</span> <span class="n">token</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="try-signing-in-using-passwordless">Try signing in using passwordless</h2>
<p>Now that we can successfully register a passwordless credential, you might want to try using it as well. The procedure is very similar to registering.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script </span><span class="na">src=</span><span class="s">"https://cdn.passwordless.dev/dist/0.0.1/passwordlessclient.min.js"</span> <span class="na">integrity=</span><span class="s">"sha384-TPor6eIWM4IefSReNrio8zR0tr3LIHYNSwlSNKArZo42TEWTmByjkkJm/vvnUxxv"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">></script></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"username"</span> <span class="na">placeholder=</span><span class="s">"Your username"</span> <span class="nt">/></span>
<span class="nt"><button</span> <span class="na">id=</span><span class="s">"passwordless-signin"</span><span class="nt">></span>Sign in passwordless<span class="nt"></button></span>
<span class="nt"><script></span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">PasswordlessSignin</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">({</span>
<span class="na">apiKey</span><span class="p">:</span> <span class="dl">"</span><span class="s2">demo:public:xxx</span><span class="dl">"</span>
<span class="p">});</span>
<span class="kd">var</span> <span class="nx">username</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">username</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="p">;</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">p</span><span class="p">.</span><span class="nx">signin</span><span class="p">(</span><span class="nx">username</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">verifiedUser</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">/example-backend/signin?token=</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">token</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=></span> <span class="nx">r</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
<span class="c1">// If no error is thrown, the sign in was successful!</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">User</span><span class="dl">"</span><span class="p">,</span> <span class="nx">verifiedUser</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Things went really bad: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">passwordless-signin</span><span class="dl">'</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="nx">PasswordlessSignin</span><span class="p">);</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>Similar to when registering a client, we need to add some backend code to verify the token and user.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// File: AccountController.cs (or other suitable controller)</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IActionResult</span><span class="p">></span> <span class="nf">TokenVerify</span><span class="p">(</span><span class="kt">string</span> <span class="n">token</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">apiSecret</span> <span class="p">=</span> <span class="s">"demo:secret:yyy"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">httpClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">();</span>
<span class="c1">// use the Passwordless extension functions</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="nf">VerifyPasswordlessToken</span><span class="p">(</span><span class="k">new</span> <span class="nf">VerifyTokenParameters</span><span class="p">(</span><span class="n">apiSecret</span><span class="p">,</span> <span class="n">token</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">Success</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// The sign in was successful, set any authentication cookies etc</span>
<span class="n">Response</span><span class="p">.</span><span class="n">Cookies</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="s">"User"</span><span class="p">,</span> <span class="n">result</span><span class="p">.</span><span class="n">Username</span><span class="p">);</span>
<span class="cm">/* These are the values returned by the API:
public string Username - The username of the signed in user
DateTime ExpiresAt - When the Token expires/expired at
public DateTime Timestamp - When the sign in took place
public string RPID - For what domain the user signed in to (example.com)
public string Origin - The Origin that the user signed in to (https://auth.example.com)
public bool Success - If the sign in was successful
public string Device - What device was used , e.g. "Firefox, Windows 10"
public string Country - What country the sign in came from, e.g. "US".
public string Nickname - Nickname of the credential, can be supplied when registering it.
*/</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>That’s it! You can now allow users to sign in using whatever security mechanism is available on their device (FaceID, TouchID, Windows Hello etc).</p>
<p><a href="https://ideasof.andersaberg.com/development/passwordless-api">Sign in with fingerprint - asp.net</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on November 23, 2020.</p>
https://ideasof.andersaberg.com/development/performance-counters-and-azure-appservices
https://ideasof.andersaberg.com/development/performance-counters-and-azure-appservices2020-01-09T00:00:00-05:002020-01-09T00:00:00-05:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>I’ve recently had the unfortunate task to diagnose and monitor memory and GC related events for our asp.net api running on .net 4.7.2 in Azure App Services (Azure Web app). Information and examples on how to do this is hard to come by and perhaps not relevant due to the difference in azure appservices environment, so I thought I’d summarize my experience in Jan 2020. <em>Note: I’m not sure if this post applies to .net core.</em></p>
<h2 id="what-performance-counters-are-interesting">What Performance Counters are interesting?</h2>
<p>I don’t know about you, but this is the first time I’m using Performance Counters although I’ve “heard about them” before. So what performance counters exist and what do they tell us? I found <a href="https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/performance-counters">this documentation</a>
very helpful in listing and understanding what they mean.</p>
<p>When diagnosing memory, I found these blog posts helpful:</p>
<ul>
<li><a href="https://medium.com/swlh/optimizing-garbage-collection-in-a-high-load-net-web-service-3bb620b444a7">Optimizing garbage collection in a high load .NET service (2019)</a></li>
<li><a href="https://www.red-gate.com/simple-talk/dotnet/net-framework/the-dangers-of-the-large-object-heap/">The Dangers of the Large Object Heap (2009)</a></li>
<li><a href="https://docs.microsoft.com/en-us/archive/blogs/mariohewardt/no-more-memory-fragmentation-on-the-net-large-object-heap">No More Memory Fragmentation on the .NET Large Object Heap (2013)</a></li>
<li><a href="https://www.dynatrace.com/news/blog/net-performance-analysis-a-net-garbage-collection-mystery/">A .NET Garbage Collection Mystery</a></li>
</ul>
<h2 id="azure-appservices-does-not-expose-all-performance-counters">Azure AppServices does not expose all Performance Counters</h2>
<p>Because Azure AppServices are running in a sandbox environment, we are not allowed to get performance counters as usual. However, <a href="https://github.com/projectkudu/kudu/wiki/Perf-Counters-exposed-as-environment-variables">a subset</a> (April, 2018) is exposed via dynamic environment variables.</p>
<p>The variables are:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">WEBSITE_COUNTERS_ASPNET</code> - Returns a JSON object containing the ASP.NET perf counters.</li>
<li><code class="language-plaintext highlighter-rouge">WEBSITE_COUNTERS_APP</code> - Returns a JSON object containing sandbox counters.</li>
<li><code class="language-plaintext highlighter-rouge">WEBSITE_COUNTERS_CLR</code> - Returns a JSON object containing CLR counters.</li>
<li><code class="language-plaintext highlighter-rouge">WEBSITE_COUNTERS_ALL</code> - Returns a JSON object containing the combination of the other three.</li>
</ul>
<p>Since the source was last updated in April 2018, I visited my site in Kudo (<em>https://example.scm.azurewebsites.net/DebugConsole/?shell=powershell</em>) and executed <code class="language-plaintext highlighter-rouge">$env:WEBSITE_COUNTERS_ALL</code> to see what counters are available in 2020.</p>
<p>Performance counters in ASP.NET Azure App Services (2020):</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">"aspNet"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"applicationRestarts"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"applicationsRunning"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsDisconnected"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestExecutionTime"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsRejected"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsQueued"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"wpsRunning"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"wpsRestarts"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestWaitTime"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsCurrent"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"globalAuditSuccess"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"globalAuditFail"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"globalEventsError"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"globalEventsHttpReqError"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"globalEventsHttpInfraError"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsInNativeQueue"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"anonymousRequests"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalCacheEntries"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalCacheTurnoverRate"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalCacheHits"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalCacheMisses"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalCacheRatioBase"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"apiCacheEntries"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"apiCacheTurnoverRate"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"apiCacheHits"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"apiCacheMisses"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"apiCacheRatioBase"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"outputCacheEntries"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"outputCacheTurnoverRate"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"outputCacheHits"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"outputCacheMisses"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"outputCacheRatioBase"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"compilations"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"debuggingRequests"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"errorsPreProcessing"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"errorsCompiling"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"errorsDuringRequest"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"errorsUnhandled"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"errorsTotal"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"pipelines"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestBytesIn"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestBytesOut"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsExecuting"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsFailed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsNotFound"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsNotAuthorized"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsInApplicationQueue"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsTimedOut"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsSucceded"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsTotal"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sessionsActive"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sessionsAbandoned"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sessionsTimedOut"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sessionsTotal"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"transactionsAborted"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"transactionsCommitted"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"transactionsPending"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"transactionsTotal"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sessionStateServerConnections"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sessionSqlServerConnections"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"eventsTotal"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"eventsApp"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"eventsError"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"eventsHttpReqError"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"eventsHttpInfraError"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"eventsWebReq"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"auditSuccess"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"auditFail"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"memberSuccess"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"memberFail"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"formsAuthSuccess"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"formsAuthFail"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"viewstateMacFail"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"appRequestExecTime"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"appRequestDisconnected"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"appRequestsRejected"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"appRequestWaitTime"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"cachePercentMachMemLimitUsed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"cachePercentMachMemLimitUsedBase"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"cachePercentProcMemLimitUsed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"cachePercentProcMemLimitUsedBase"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"cacheTotalTrims"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"cacheApiTrims"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"cacheOutputTrims"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"appCpuUsed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"appCpuUsedBase"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"appMemoryUsed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestBytesInWebsockets"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestBytesOutWebsockets"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsExecutingWebsockets"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsFailedWebsockets"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsSucceededWebsockets"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"requestsTotalWebsockets"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"app"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"userTime"</span><span class="p">:</span><span class="w"> </span><span class="mi">81718750</span><span class="p">,</span><span class="w">
</span><span class="nl">"kernelTime"</span><span class="p">:</span><span class="w"> </span><span class="mi">74531250</span><span class="p">,</span><span class="w">
</span><span class="nl">"pageFaults"</span><span class="p">:</span><span class="w"> </span><span class="mi">447720</span><span class="p">,</span><span class="w">
</span><span class="nl">"processes"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
</span><span class="nl">"processLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"threads"</span><span class="p">:</span><span class="w"> </span><span class="mi">91</span><span class="p">,</span><span class="w">
</span><span class="nl">"threadLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"connections"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"connectionLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sections"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"sectionLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"namedPipes"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
</span><span class="nl">"namedPipeLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">128</span><span class="p">,</span><span class="w">
</span><span class="nl">"readIoOperations"</span><span class="p">:</span><span class="w"> </span><span class="mi">3325</span><span class="p">,</span><span class="w">
</span><span class="nl">"writeIoOperations"</span><span class="p">:</span><span class="w"> </span><span class="mi">894</span><span class="p">,</span><span class="w">
</span><span class="nl">"otherIoOperations"</span><span class="p">:</span><span class="w"> </span><span class="mi">28613</span><span class="p">,</span><span class="w">
</span><span class="nl">"readIoBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">163512305</span><span class="p">,</span><span class="w">
</span><span class="nl">"writeIoBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">70659170</span><span class="p">,</span><span class="w">
</span><span class="nl">"otherIoBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">2008223</span><span class="p">,</span><span class="w">
</span><span class="nl">"privateBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">2093711360</span><span class="p">,</span><span class="w">
</span><span class="nl">"handles"</span><span class="p">:</span><span class="w"> </span><span class="mi">6769</span><span class="p">,</span><span class="w">
</span><span class="nl">"contextSwitches"</span><span class="p">:</span><span class="w"> </span><span class="mi">816796</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteOpens"</span><span class="p">:</span><span class="w"> </span><span class="mi">3427</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteWrites"</span><span class="p">:</span><span class="w"> </span><span class="mi">202</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteWriteKBs"</span><span class="p">:</span><span class="w"> </span><span class="mi">10058</span><span class="p">,</span><span class="w">
</span><span class="nl">"availMemoryBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">11285364736</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteDirMonitors"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteDirMonitorLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"activeConnections"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"activeConnectionLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"clr"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"bytesInAllHeaps"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"gcHandles"</span><span class="p">:</span><span class="w"> </span><span class="mi">654</span><span class="p">,</span><span class="w">
</span><span class="nl">"gen0Collections"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"gen1Collections"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"gen2Collections"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"inducedGC"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"pinnedObjects"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"committedBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"reservedBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"timeInGC"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"timeInGCBase"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"allocatedBytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"gen0HeapSize"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"gen1HeapSize"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"gen2HeapSize"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"largeObjectHeapSize"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"currentAssemblies"</span><span class="p">:</span><span class="w"> </span><span class="mi">19</span><span class="p">,</span><span class="w">
</span><span class="nl">"currentClassesLoaded"</span><span class="p">:</span><span class="w"> </span><span class="mi">400</span><span class="p">,</span><span class="w">
</span><span class="nl">"exceptionsThrown"</span><span class="p">:</span><span class="w"> </span><span class="mi">14</span><span class="p">,</span><span class="w">
</span><span class="nl">"appDomains"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"appDomainsUnloaded"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"adonet"</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>
<h2 id="reporting-to-application-insights">Reporting to Application Insights</h2>
<p>You could write some .NET to fetch these variables and log them as you see fit. But since I already use Application Insights for telemtetry I wanted to make sure ApplicationInsights log the counters I’m interested in.</p>
<p>To do this, I modified my <code class="language-plaintext highlighter-rouge">ApplicationInsights.config</code> file.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><ApplicationInsights</span> <span class="na">xmlns=</span><span class="s">"http://schemas.microsoft.com/ApplicationInsights/2013/Settings"</span><span class="nt">></span>
<span class="c"><!-- ...omitted for brevity --></span>
<span class="nt"><TelemetryModules></span>
<span class="c"><!-- ...omitted for brevity --></span>
<span class="nt"><Add</span> <span class="na">Type=</span><span class="s">"Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule, Microsoft.AI.PerfCounterCollector"</span><span class="nt">></span>
<span class="c"><!--
Use the following syntax here to collect additional performance counters:
<Counters>
<Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Handle Count" ReportAs="Process handle count" />
...
</Counters>
PerformanceCounter must be either \CategoryName(InstanceName)\CounterName or \CategoryName\CounterName
NOTE: performance counters configuration will be lost upon NuGet upgrade.
The following placeholders are supported as InstanceName:
??APP_WIN32_PROC?? - instance name of the application process for Win32 counters.
??APP_W3SVC_PROC?? - instance name of the application IIS worker process for IIS/ASP.NET counters.
??APP_CLR_PROC?? - instance name of the application CLR process for .NET counters.
--></span>
<span class="nt"><Counters></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\% Time in GC"</span> <span class="na">ReportAs=</span><span class="s">"% Time In GC"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\# Gen 0 Collections"</span> <span class="na">ReportAs=</span><span class="s">"# Gen 0 Collections"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\# Gen 1 Collections"</span> <span class="na">ReportAs=</span><span class="s">"# Gen 1 Collections"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\# Gen 2 Collections"</span> <span class="na">ReportAs=</span><span class="s">"# Gen 2 Collections"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\Gen 0 heap size"</span> <span class="na">ReportAs=</span><span class="s">"Gen 0 Heapsize"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\Gen 1 heap size"</span> <span class="na">ReportAs=</span><span class="s">"Gen 1 Heapsize"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\Gen 2 heap size"</span> <span class="na">ReportAs=</span><span class="s">"Gen 2 Heapsize"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\Large Object Heap size"</span> <span class="na">ReportAs=</span><span class="s">"Large Object Heap Size"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\# Induced GC"</span> <span class="na">ReportAs=</span><span class="s">"# Induced GC"</span> <span class="nt">/></span>
<span class="nt"><Add</span> <span class="na">PerformanceCounter=</span><span class="s">"\.NET CLR Memory(??APP_CLR_PROC??)\Allocated Bytes/ sec"</span> <span class="na">ReportAs=</span><span class="s">"Allocated Bytes/ Sec"</span> <span class="nt">/></span>
<span class="nt"></Counters></span>
<span class="nt"></Add></span>
<span class="c"><!-- ...omitted for brevity --></span>
</code></pre></div></div>
<p>With this config I could publish the webapp to azure. As you can see, I’ve added a number of Performance Counters without referencing the environment variables I mentioned earlier. That’s because Application Insights has support to automatically translate these counter names and get the data from the the environment variables.</p>
<p><strong>But what counters does Application Insights support and what are they named?</strong> Which environment variables is used for what counter? To find out about this I had to dig through the <a href="https://github.com/microsoft/ApplicationInsights-dotnet">source code</a> of the SDK to find the <code class="language-plaintext highlighter-rouge">CounterFactory.cs</code> class that <a href="https://github.com/microsoft/ApplicationInsights-dotnet/blob/35e4bb2624bcbe2af9fb5d1e724b28ffd49c460c/WEB/Src/PerformanceCollector/Perf.Shared/Implementation/WebAppPerformanceCollector/CounterFactory.cs">lists all performance counters supported in asp.net</a></p>
<h2 id="querying-and-visualizing-the-performance-counters">Querying and visualizing the Performance counters</h2>
<p>To check if the metrics are reported correctly I turn to my Application Insights Logs (Analytics) and ran the following query to see which counters were being reported:</p>
<pre><code class="language-kql">performanceCounters
| distinct name, counter
</code></pre>
<p>Below are some queries I find useful.</p>
<h3 id="view-heapsizes-over-time">View Heapsizes over time</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>performanceCounters
| where name contains "Gen 2 Heapsize" or name contains "Gen 1 Heapsize" or name contains "Gen 0 Heapsize" or name contains "Large Object Heap Size"
| project timestamp, name, value = value / 1000000, cloud_RoleInstance, cloud_RoleName
| evaluate pivot(name, avg(value), timestamp)
| render barchart
</code></pre></div></div>
<p><img src="/images/perfCounters/heapsize.png" alt=""Heapsizes over time"" /></p>
<h3 id="view-new-collections-over-time">View new Collections over time</h3>
<p>The Gen Collection counter is an ever incrementing number. To visualize when and how many collections are being actually made, you can use the following query that compares a value to the previous value.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>performanceCounters
| where name contains "Collections"
| order by timestamp asc
| evaluate pivot(name, avg(value), timestamp, cloud_RoleName, cloud_RoleInstance)
| extend g0 = ["# Gen 0 Collections"]
| extend g1 = ["# Gen 1 Collections"]
| extend g2 = ["# Gen 2 Collections"]
| serialize
| extend new_g0 = max_of(g0 - prev(g0,1,0), real(0))
| extend new_g1 = max_of(g1 - prev(g1,1,0), real(0))
| extend new_g2 = max_of(g2 - prev(g2,1,0), real(0))
| project timestamp, new_g0, new_g1, new_g2
| render barchart
</code></pre></div></div>
<p><img src="/images/perfCounters/collections.png" alt=""Heapsizes over time"" /></p>
<h2 id="view--cpu-time-in-gc-over-time">View % CPU Time in GC over time</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>performanceCounters
| where name contains "% Time in GC"
| project timestamp, name, value, cloud_RoleInstance, cloud_RoleName
</code></pre></div></div>
<p><img src="/images/perfCounters/cputime.png" alt=""% CPU Time in GC over time"" /></p>
<h2 id="final-notes">Final notes</h2>
<p>I hope this helped you if you are in the same position I was in. If you think I’ve gotten anything backwards, please tell me so on twitter <a href="https://twitter.com/andersaberg">@andersaberg</a>. Feedback is always welcome.</p>
<p><a href="https://ideasof.andersaberg.com/development/performance-counters-and-azure-appservices">How to collect and visualize performance counters for ASP.NET on Azure App Services</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on January 09, 2020.</p>
https://ideasof.andersaberg.com/development/personal-access-tokens-with-identityserver
https://ideasof.andersaberg.com/development/personal-access-tokens-with-identityserver2019-12-27T00:00:00-05:002019-12-27T00:00:00-05:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>In this post, I will describe how you can leverage existing IdentityServer features to generate and support PATs, as well as configuring your API Resources to accept them.</p>
<h2 id="why-personal-access-tokens-pat">Why Personal Access Tokens (PAT)?</h2>
<p>A PAT is a alternative to your username/password for authentication when working with Automation scripts or curl’ing your API where oAuth might be inconvenient or hard to implement.</p>
<h2 id="reference-tokens">Reference Tokens</h2>
<p>Identityserver has built-in support to generate both JWT (Self-contained) and <a href="http://docs.identityserver.io/en/latest/topics/reference_tokens.html">Reference Tokens</a> (not self-contained). In contrast to JWTs, a reference token can easily be revoked which is a useful feature for a PAT. IdentityServer also expose introspection endpoints for oAuth API Resources to verify the validity of a Reference Token. We can therefore leverage long-lived reference tokens as PATs.</p>
<h3 id="self-issue-reference-tokens-in-code">Self-issue reference tokens in code</h3>
<p>First we need an easy way for a user to generate a PAT. Luckily, IdentityServer already comes with tools to <a href="http://docs.identityserver.io/en/latest/topics/tools.html">self-issue tokens</a>.
However, since those APIs generate JWTs I had to create my own tools. Easy enough since the original source code is available on github. Here is my customized version:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">/// <summary></span>
<span class="c1">/// Class for useful helpers for interacting with IdentityServer</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">TokenTools</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">ITokenService</span> <span class="n">_tokenCreation</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">ISystemClock</span> <span class="n">_clock</span><span class="p">;</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Initializes a new instance of the <see cref="IdentityServerTools" /> class.</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <param name="contextAccessor">The context accessor.</param></span>
<span class="c1">/// <param name="tokenCreation">The token creation service.</param></span>
<span class="c1">/// <param name="clock">The clock.</param></span>
<span class="k">public</span> <span class="nf">TokenTools</span><span class="p">(</span><span class="n">ITokenService</span> <span class="n">defaultTokenService</span><span class="p">,</span> <span class="n">ISystemClock</span> <span class="n">clock</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_tokenCreation</span> <span class="p">=</span> <span class="n">defaultTokenService</span><span class="p">;</span>
<span class="n">_clock</span> <span class="p">=</span> <span class="n">clock</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Issues a JWT.</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <param name="lifetime">The lifetime.</param></span>
<span class="c1">/// <param name="issuer">The issuer.</param></span>
<span class="c1">/// <param name="claims">The claims.</param></span>
<span class="c1">/// <returns></returns></span>
<span class="c1">/// <exception cref="System.ArgumentNullException">claims</exception></span>
<span class="k">public</span> <span class="k">virtual</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">IssueReferenceToken</span><span class="p">(</span><span class="kt">int</span> <span class="n">lifetime</span><span class="p">,</span> <span class="kt">string</span> <span class="n">issuer</span><span class="p">,</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Claim</span><span class="p">></span> <span class="n">claims</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">issuer</span><span class="p">))</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">issuer</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">claims</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">claims</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">token</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Token</span>
<span class="p">{</span>
<span class="n">Audiences</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">string</span><span class="p">[]</span> <span class="p">{</span> <span class="s">"my_api"</span> <span class="p">},</span>
<span class="n">ClientId</span> <span class="p">=</span> <span class="s">"pat_client"</span><span class="p">,</span>
<span class="n">CreationTime</span> <span class="p">=</span> <span class="n">_clock</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">.</span><span class="n">UtcDateTime</span><span class="p">,</span>
<span class="n">Issuer</span> <span class="p">=</span> <span class="n">issuer</span><span class="p">,</span>
<span class="n">Lifetime</span> <span class="p">=</span> <span class="n">lifetime</span><span class="p">,</span>
<span class="n">Type</span> <span class="p">=</span> <span class="n">OidcConstants</span><span class="p">.</span><span class="n">TokenTypes</span><span class="p">.</span><span class="n">AccessToken</span><span class="p">,</span>
<span class="n">AccessTokenType</span> <span class="p">=</span> <span class="n">AccessTokenType</span><span class="p">.</span><span class="n">Reference</span><span class="p">,</span>
<span class="n">Claims</span> <span class="p">=</span> <span class="k">new</span> <span class="n">HashSet</span><span class="p"><</span><span class="n">Claim</span><span class="p">>(</span><span class="n">claims</span><span class="p">,</span> <span class="k">new</span> <span class="nf">ClaimComparer</span><span class="p">())</span>
<span class="p">};</span>
<span class="kt">var</span> <span class="n">handle</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_tokenCreation</span><span class="p">.</span><span class="nf">CreateSecurityTokenAsync</span><span class="p">(</span><span class="n">token</span><span class="p">);</span>
<span class="k">return</span> <span class="n">handle</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The method <code class="language-plaintext highlighter-rouge">IssueReferenceToken</code> accepts parameters to customize the lifetime, the issuer and the claims of the token and returns a string token back. While we could make Audiences and clientId to be customizeable it was not needed in my use case.</p>
<h3 id="adding-a-pat-client">Adding a PAT Client</h3>
<p>Since the PAT might be used by a script/application that is not registered as a client in Identityserver I created a “default” client for PATs. Useful if you want to limit scopes etc.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">new</span> <span class="n">Client</span>
<span class="p">{</span>
<span class="n">ClientId</span> <span class="p">=</span> <span class="s">"pat_client"</span><span class="p">,</span>
<span class="n">ClientName</span> <span class="p">=</span> <span class="s">"Personal Access Token Public Client"</span><span class="p">,</span>
<span class="n">AllowedScopes</span> <span class="p">=</span>
<span class="p">{</span>
<span class="n">IdentityServerConstants</span><span class="p">.</span><span class="n">StandardScopes</span><span class="p">.</span><span class="n">OpenId</span><span class="p">,</span>
<span class="n">IdentityServerConstants</span><span class="p">.</span><span class="n">StandardScopes</span><span class="p">.</span><span class="n">Profile</span><span class="p">,</span>
<span class="s">"my_api"</span>
<span class="p">},</span>
<span class="n">AccessTokenType</span> <span class="p">=</span> <span class="n">AccessTokenType</span><span class="p">.</span><span class="n">Reference</span>
<span class="p">},</span>
</code></pre></div></div>
<h2 id="list-and-create-pats">List and Create PATs</h2>
<p>I added a PATController with some very simple Views that allow the user to Create / List PATs.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IActionResult</span><span class="p">></span> <span class="nf">Index</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">string</span> <span class="n">subject</span> <span class="p">=</span> <span class="n">HttpContext</span><span class="p">.</span><span class="n">User</span><span class="p">.</span><span class="nf">GetSubjectId</span><span class="p">();</span>
<span class="c1">// customized GrantStore method to get all PATs</span>
<span class="kt">var</span> <span class="n">pats</span> <span class="p">=</span> <span class="k">await</span> <span class="n">referencetokenStore</span><span class="p">.</span><span class="nf">GetGrantsAsync</span><span class="p">(</span><span class="n">subject</span><span class="p">);</span>
<span class="k">return</span> <span class="nf">View</span><span class="p">(</span><span class="n">pats</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IActionResult</span><span class="p">></span> <span class="nf">Create</span><span class="p">(</span><span class="kt">string</span> <span class="n">shortName</span><span class="p">,</span> <span class="n">DateTime</span> <span class="n">expiration</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">double</span> <span class="n">PatExpires</span> <span class="p">=</span> <span class="p">(</span><span class="n">expiration</span> <span class="p">-</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">).</span><span class="n">TotalSeconds</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">PatExpires</span> <span class="p">></span> <span class="kt">int</span><span class="p">.</span><span class="n">MaxValue</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// correctly adjust for int overflow</span>
<span class="c1">// int.MaxValue; // 68 years, maximum</span>
<span class="n">PatExpires</span> <span class="p">=</span> <span class="kt">int</span><span class="p">.</span><span class="n">MaxValue</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// todo: Support multiple clients?</span>
<span class="kt">var</span> <span class="n">clientId</span> <span class="p">=</span> <span class="s">"pat_client"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">scopes</span> <span class="p">=</span> <span class="s">"my_api"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">issuer</span> <span class="p">=</span> <span class="s">"xxx"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">tokenMeta</span> <span class="p">=</span> <span class="s">$"</span><span class="p">{</span><span class="n">shortName</span><span class="p">}</span><span class="s">-</span><span class="p">{</span><span class="n">DateTime</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">.</span><span class="nf">ToShortDateString</span><span class="p">()}</span><span class="s">-</span><span class="p">{</span><span class="n">DateTime</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">.</span><span class="nf">AddSeconds</span><span class="p">(</span><span class="n">PatExpires</span><span class="p">).</span><span class="nf">ToShortDateString</span><span class="p">()}</span><span class="s">"</span><span class="p">;</span>
<span class="n">IEnumerable</span><span class="p"><</span><span class="n">Claim</span><span class="p">></span> <span class="n">claims</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Claim</span><span class="p">[]</span> <span class="p">{</span>
<span class="c1">// user subject</span>
<span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">JwtClaimTypes</span><span class="p">.</span><span class="n">Subject</span><span class="p">,</span> <span class="n">HttpContext</span><span class="p">.</span><span class="n">User</span><span class="p">.</span><span class="nf">GetSubjectId</span><span class="p">()),</span>
<span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="n">JwtClaimTypes</span><span class="p">.</span><span class="n">Scope</span><span class="p">,</span> <span class="n">scopes</span><span class="p">),</span>
<span class="c1">// what auth was made? pat, custom value by anders</span>
<span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="s">"amr"</span><span class="p">,</span> <span class="s">"pat"</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">Claim</span><span class="p">(</span><span class="s">"token_meta"</span><span class="p">,</span> <span class="n">tokenMeta</span><span class="p">)</span>
<span class="c1">// etc</span>
<span class="p">};</span>
<span class="kt">var</span> <span class="n">token</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_tools</span><span class="p">.</span><span class="nf">IssueReferenceToken</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">PatExpires</span><span class="p">,</span> <span class="n">issuer</span><span class="p">,</span> <span class="n">claims</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">msg</span> <span class="p">=</span> <span class="s">$@"This is your Personal Access Token (PAT). It will only be shown once: </span><span class="err">
</span><span class="p">{</span><span class="n">tokenMeta</span><span class="p">}</span><span class="s">--</span><span class="p">{</span><span class="n">token</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>
<span class="k">return</span> <span class="nf">Content</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IActionResult</span><span class="p">></span> <span class="nf">Remove</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// ommitted for brevity}</span>
</code></pre></div></div>
<h2 id="modify-handles-optional">Modify handles (optional)</h2>
<p>When creating the reference token i add a token_meta claim and put that same meta information on the token i return to the user. I add this information to make the tokens more developer friendly, displaying a shortname and ceration/expiration in the token itself. I find that this makes them a lot more maintable when used in apps or scripts.</p>
<p>E.g. With meta info the token explains to any developer reading it that it was created by anders and it will expire 2020:<br />
<code class="language-plaintext highlighter-rouge">andersfullaccess-2019-12-01-2020-12-01--FaGsj3J0xdjVhafbNy4hL328Idjhasks82xq</code><br />
compared to <code class="language-plaintext highlighter-rouge">FaGsj3J0xdjVhafbNy4hL328Idjhasks82xq</code>.</p>
<p>This is optional but I think a good thing todo.</p>
<p>In order to support the customized token handles (and to be able to list all tokens for a user) we have to customize the <code class="language-plaintext highlighter-rouge">DefaultReferencetokenStore</code>.</p>
<h2 id="custom-defaultreferencetokenstore">Custom DefaultReferencetokenStore</h2>
<p>Since the default implementation lacks methods to retrieve all tokens and support or meta info I needed to add my own implementation with extra methods.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">/// <summary></span>
<span class="c1">/// Customized ReferenceTokenStore to handle</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">CustomReferencetokenStore</span> <span class="p">:</span> <span class="n">DefaultReferenceTokenStore</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">IPersistedGrantStore</span> <span class="n">store</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">CustomReferencetokenStore</span><span class="p">(</span><span class="n">IPersistedGrantStore</span> <span class="n">store</span><span class="p">,</span> <span class="n">IPersistentGrantSerializer</span> <span class="n">serializer</span><span class="p">,</span> <span class="n">IHandleGenerationService</span> <span class="n">handleGenerationService</span><span class="p">,</span> <span class="n">ILogger</span><span class="p"><</span><span class="n">DefaultReferenceTokenStore</span><span class="p">></span> <span class="n">logger</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">store</span><span class="p">,</span> <span class="n">serializer</span><span class="p">,</span> <span class="n">handleGenerationService</span><span class="p">,</span> <span class="n">logger</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">store</span> <span class="p">=</span> <span class="n">store</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Get all grants by subject</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <param name="subject"></param></span>
<span class="c1">/// <returns></returns></span>
<span class="k">internal</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">PersistedGrant</span><span class="p">>></span> <span class="nf">GetGrantsAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">subject</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">all</span> <span class="p">=</span> <span class="k">await</span> <span class="n">store</span><span class="p">.</span><span class="nf">GetAllAsync</span><span class="p">(</span><span class="n">subject</span><span class="p">);</span>
<span class="k">return</span> <span class="n">all</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">/// <summary></span>
<span class="c1">/// Gets a Token using the handle format but allow the "--" format</span>
<span class="c1">/// </summary></span>
<span class="c1">/// <param name="handle"></param></span>
<span class="c1">/// <returns></returns></span>
<span class="k">protected</span> <span class="k">override</span> <span class="n">Task</span><span class="p"><</span><span class="n">Token</span><span class="p">></span> <span class="nf">GetItemAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">handle</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// clean up our metainfo</span>
<span class="k">if</span><span class="p">(</span><span class="n">handle</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="s">"--"</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">handle</span> <span class="p">=</span> <span class="n">handle</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="s">"--"</span><span class="p">)[</span><span class="m">1</span><span class="p">];</span> <span class="c1">// skip metadata before --</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">base</span><span class="p">.</span><span class="nf">GetItemAsync</span><span class="p">(</span><span class="n">handle</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// ommitted for brevity</span>
</code></pre></div></div>
<h2 id="adjusting-tokenlength-restrictions-optional">Adjusting TokenLength restrictions (optional)</h2>
<p>Although not required, depending on how long your meta info is (and if it’s user input) you might need to allow some extra length on token handles.</p>
<p>Just adjust the options like this when configuring IdentityServer in <code class="language-plaintext highlighter-rouge">Startup.cs</code>:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddIdentityServer</span><span class="p">(</span><span class="n">options</span> <span class="p">=></span> <span class="p">{</span>
<span class="c1">// Allow extra space for descriptions in our custom reference token handles</span>
<span class="n">options</span><span class="p">.</span><span class="n">InputLengthRestrictions</span><span class="p">.</span><span class="n">TokenHandle</span> <span class="p">=</span> <span class="m">150</span><span class="p">;</span> <span class="c1">// default is 100</span>
<span class="p">})</span>
</code></pre></div></div>
<h2 id="ipersistedgrantstore">IPersistedGrantStore</h2>
<p>Since you probably want your PATs to be active after a restart of your IdentityServer you need to persist them. Luckily IdentityServer already persists your reference tokens using IPersistedGrantStore. However, by default it’s only in memory. I implemented a IPersistedGrantStore to use SQL. You can read how to do that here: <a href="https://mcguirev10.com/2018/01/02/identityserver4-without-entity-framework.html">identityserver4-without-entityframework</a></p>
<h2 id="registering-your-methods-in-the-di">Registering your methods in the DI</h2>
<p>Don’t forget to register your new <code class="language-plaintext highlighter-rouge">CustomReferencetokenStore</code>, <code class="language-plaintext highlighter-rouge">IPersistedGrantStore</code> so that IdentityServer uses them:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//config.cs</span>
<span class="n">services</span><span class="p">.</span><span class="n">AddTransient</span><span class="p"><</span><span class="n">CustomReferencetokenStore</span><span class="p">>();</span>
<span class="n">services</span><span class="p">.</span><span class="n">AddTransient</span><span class="p"><</span><span class="n">IReferenceTokenStore</span><span class="p">,</span> <span class="n">CustomReferencetokenStore</span><span class="p">>();</span>
<span class="c1">// use our persistance of grants</span>
<span class="n">services</span><span class="p">.</span><span class="n">AddTransient</span><span class="p"><</span><span class="n">IPersistedGrantStore</span><span class="p">,</span> <span class="n">PersistedGrantStore</span><span class="p">>();</span>
</code></pre></div></div>
<h2 id="configure-your-api-resource-to-accept-your-new-pat-tokens">Configure your API Resource to accept your new PAT tokens</h2>
<p>Since the PATs are standard oauth reference_tokens, you just need to be sure you’ve configured your resource to have access to the identityserver <a href="http://docs.identityserver.io/en/latest/endpoints/introspection.html">introspection endpoint</a>.</p>
<p>Make sure you set up clientId, clientSecret and that you allow both JWTs and reference tokens:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">app</span><span class="p">.</span><span class="nf">UseIdentityServerBearerTokenAuthentication</span><span class="p">(</span><span class="k">new</span> <span class="n">IdentityServerBearerTokenAuthenticationOptions</span>
<span class="p">{</span>
<span class="n">Authority</span> <span class="p">=</span> <span class="s">"yourserver.io"</span>
<span class="n">ClientId</span> <span class="p">=</span><span class="s">"my_api"</span><span class="p">,</span>
<span class="n">ClientSecret</span> <span class="p">=</span> <span class="s">"my_api_secret"</span><span class="p">,</span>
<span class="n">ValidationMode</span> <span class="p">=</span> <span class="n">ValidationMode</span><span class="p">.</span><span class="n">Both</span> <span class="c1">// default value</span>
<span class="p">});</span>
</code></pre></div></div>
<h2 id="try-it-out">Try it out</h2>
<p>Your identityserver should now have a controller to issue and list long lived reference tokens and your API Resource should be configured to accept them. Try it out by calling your api with <code class="language-plaintext highlighter-rouge">Authorization: Bearer andersfullaccess-2019-12-01-2020-12-01--FaGsj3J0xdjVhafbNy4hL328Idjhasks82xq</code> or using the <a href="https://identitymodel.readthedocs.io/en/latest/client/introspection.html">introspection client directly</a>. The logging in IdentityServer is very good, if the tokens are invalid the logs should help you diagnose where the problem lies.</p>
<h2 id="todo">Todo</h2>
<p>By adding code to the controller and the CustomReferencetokenStore you can implement a feature to revoke/remove PATs.
Another thing I’d like to add, to help users managing their PATS is adding LastUsed timestamps to each PAT. This could be done in our custom store.</p>
<p>Depending on your use case, allowing a user to customize what scopes are added to the token could be very powerful and I would encourage it. This would enable a user to issue read-only tokens, or to give a script access to only a certain feature instead of everything.</p>
<h2 id="summary">Summary</h2>
<p>We’ve now seen how IdentityServer features could be re-used to enable long lived Personal Access Tokens. PATs are a great <em>alternative</em> to oAuth flows when you need to authenticate in scripts or custom made automation where oAuth support is inconvenient.</p>
<p>The PATs we have generated are long lived and easy to revoke. They can contain custom claims and meta-info. Please reach out with any feedback or suggestions <a href="https://twitter.com/andersaberg">@andersaberg</a></p>
<p><a href="https://ideasof.andersaberg.com/development/personal-access-tokens-with-identityserver">Personal Access Tokens with IdentityServer4</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on December 27, 2019.</p>
https://ideasof.andersaberg.com/development/jquery-in-three-lines-of-code
https://ideasof.andersaberg.com/development/jquery-in-three-lines-of-code2019-01-13T00:00:00-05:002019-01-13T00:00:00-05:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>One code snippet I often return to when building something small and I need to select an element or add eventhandler is this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
const $ = (selector) => document.querySelector(selector)
const $$ = (selector) => document.querySelectorAll(selector)
const on = (elem, type, listener) => elem.addEventListener(type,listener)
</code></pre></div></div>
<p><a href="https://ideasof.andersaberg.com/development/jquery-in-three-lines-of-code">jQuery in three lines of code</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on January 13, 2019.</p>
https://ideasof.andersaberg.com/development/the-passwordless-web
https://ideasof.andersaberg.com/development/the-passwordless-web2018-10-28T00:00:00-04:002018-10-28T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>I have been working on a very exciting <a href="https://github.com/abergs/fido2-net-lib">side project</a> the last couple of months that will allow you - as a user of a website or app - to signup and login without passwords.</p>
<p><img src="/images/fido2/fido2.gif" alt="Fido2 register" /></p>
<p>This technology is called <em>FIDO2</em> (Fast IDentity Online) and was designed by the FIDO Alliance together with large tech companies such as Microsoft, Google, Paypal etc.</p>
<h1 id="why-its-cool-and-you-will-like-it">Why it’s cool and you will like it</h1>
<p><strong>You never have to use passwords again. Seriously.</strong> Instead you will have a key - you can think of it like a house key; <em>but digital and on steroids</em>. This digital house key can look like a usb stick (shown below) and is usable over NFC and bluetooth, but it can also be directly built into your device so you don’t need to carry something extra with you.</p>
<p>While I could say that your passwords live in this key, it’s not really true. The key uses public/private key cryptography instead of shared secrets (passwords) and your private key never leaves the device. There’s a lot more technical things to say here, but we will skip it for now.</p>
<p>Never using passwords is one of the main value propositions behind FIDO2. While that makes life a lot easier (a lot less tech support calls from the family?) it also makes you and your company a lot more secure since the majority of IT breaches and hacks are caused by leaked passwords or phising attacks, something FIDO2 eliminates.</p>
<p>It can look like this:</p>
<p>On screen:
<img src="/images/fido2/fido2-onscreen.gif" alt="Fido2 on-screen UI" /></p>
<p>Off screen:<br />
<img src="/images/fido2/fido2-offscreen.gif" alt="Fido2 off-screen UI" /></p>
<p>While the example above uses a USB stick, it can also be directly built into your phone or laptop and secured by fingerprint scanning or facial recognitnion.</p>
<h1 id="my-work">My work</h1>
<p>I started working on my <a href="https://github.com/abergs/fido2-net-lib">FIDO2 project</a> in the spring of 2018 after watching a presentation on the concept and I was very excited about the possibility of making a complex digital life easier.</p>
<p>Shortly after I began working on the open source project a security engineer joined me and together we created the first implementation of FIDO2 in .NET CORE, enabling developers to make their websites, apps and systems FIDO2 ready and compliant. We’re now launching version 1.0.0 and I couldn’t be more proud of the work we’ve done together.</p>
<p>If you are curious the project is open source and released under the MIT license here: <a href="https://github.com/abergs/fido2-net-lib">WebAuthn .net core FIDO2</a></p>
<p><a href="https://ideasof.andersaberg.com/development/the-passwordless-web">The passwordless web is coming</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on October 28, 2018.</p>
https://ideasof.andersaberg.com/development/fido2-net-library
https://ideasof.andersaberg.com/development/fido2-net-library2018-08-19T00:00:00-04:002018-08-19T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>Over the last couple of months I’ve been building a <a href="https://github.com/abergs/fido2-net-lib/">FIDO2 open source server</a>.</p>
<p>I should probably do a larger write-up on the library and FIDO itself some day, but I wanted to write down some things I had to discover and learn.</p>
<p><strong>Disclosure:</strong> I’m not an expert on FIDO. I implemented a library by reading the specs and asking a ton of questions. Before May 2018 I had never heard of Fido. I might get things wrong.</p>
<h2 id="1-fido2-is-a-separate-but-similiar-standard-from-fido-u2f-and-fido-uaf">1. FIDO2 is a separate but similiar standard from FIDO U2F and FIDO UAF.</h2>
<p>FIDO2 is a new specification, complimenting (replacing) the older FIDO U2F (second factor) and FIDO UAF (paswordless) specs and usecases.</p>
<p>FIDO2 consists of two sub specifications:</p>
<ul>
<li><a href="https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.pdf">CTAP</a> - Client to Authenticator Protocol</li>
<li><a href="https://www.w3.org/TR/webauthn/">WebAuthn</a> - Browser to Client (and kind of Browser to your FIDO Server, also called the <em>Relying party</em>)</li>
</ul>
<p>If you have a FIDO2 U2F Security keys, they can still be used for WebAuthn because FIDO2 brings backwards compatability. FIDO2 allows you to do what UAF did, but the protocol is different.</p>
<h2 id="2-attestation-vs-assertion">2. Attestation vs Assertion</h2>
<p>At first I had troubles separating these two. So here’s to anyone else wondering:</p>
<ul>
<li>Attestation is the process for registering the public key credential at the Relying Party and verifying the authenticator (more on verifying the authenticator soon).</li>
<li>Assertion is the process of authenticating a user, utilizing the registered public key to verify a signature</li>
</ul>
<p>Both of these processes have two separate stages, <em>Options</em> and <em>Result</em>. For both Attestation and Assertion the FIDO2 Server will first return <em>Options</em> to the client (client is for example a web browser). The client will then (after talking to the authenticator, using CTAP) return a <em>result</em> that the server can verify.</p>
<p>The options include things such as timeout, userID, a challenge (to be signed) etc. See <a href="https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialcreationoptions">Attestation options</a> and <a href="https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions">Assertion options</a>.</p>
<h2 id="2-none-indirect-or-direct---the-attestation-conveyance">2. None, Indirect or Direct - the attestation conveyance</h2>
<p>This was confusing. Every FIDO2 example allowed me to pick either “none”, “indirect” or “direct” but I never understood what I was picking or why. Let me try to break it down.</p>
<p>Basically the attestation process spends a lot of steps verifying that the authenticator used by the client can be trusted by the server/relying party. What kind of authenticator you believe is trustworthy depends on your security policies.</p>
<p>To quote <a href="https://twitter.com/apowers313/status/1026182636912304128">Adam Powers</a>:</p>
<blockquote>
<p>It’s really a matter of use cases and risk tolerance. If your risk tolerance is “anything FIDO is better than passwords” and you aren’t going to look at authenticator metadata “none” is fine. If you are a financial or government organization you may want attestation.</p>
</blockquote>
<p>So if you are switching from username/password to FIDO2, “none” is perfectly valid. If you are switching from Smartcards or UAF, you might be interested in Indirect/Direct. But if you are using smartcards you are probably not reading this blog.</p>
<h2 id="3-user-verification-vs-user-presence">3. User Verification vs User Presence</h2>
<p>FIDO2 by default always requires user presence. This is to stop silent logins where the User unknowingly logins / authenticate. For Security Keys it might include touching a button on the physical key to prove a (any) human is present.</p>
<p>However, a relying party can instead require User Verifcation. This means that it’s not enough for a human to be present but we want to verify that the expected human is present. For example the authenticator can require a pin code, fingerprint scan, iris scan etc.</p>
<h2 id="4-user-verification-by-pin-and-lock-out">4. User verification by PIN and lock out</h2>
<p>This one might be a little out of scope but I was curious. How many retries are allowed for getting the PIN code right?
Turns out maxmium number of incorrect pin codes are 8. When the threshold is reached the authenticator <a href="https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#client-pin-support">must be reset to a factory default state</a>.</p>
<p>Curiously, after 3 failed attempts <a href="https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#gettingPinToken">a power cycling must be done</a> to prevent malware from reseting the device without user interaction.</p>
<h2 id="5-conformance-testing-tools">5. Conformance testing tools</h2>
<p>The FIDO alliance has published some great testing tools that really helped in finding bugs in our verification alghortims. If you are building or contributing to a FIDO2 implementation, they are a necessity: <a href="https://fidoalliance.org/certification/conformance/">Get them here</a>.</p>
<h2 id="6-the-community-is-fantastic">6. The community is fantastic</h2>
<p>While documentation outside of the RFC spec is a bit sparse and sometimes hard to find, the community is a great place to ask for guidance.</p>
<p>Special thanks to <a href="https://twitter.com/apowers313">Adam Powers</a> and <a href="https://twitter.com/herrjemand">Ackermann Yuriy</a> who has been very friendly and answering questions on twitter, helping me make progress on the library.</p>
<p>A couple of months in to the project, <a href="https://twitter.com/alexseigler/">Alex Seigler</a> sent a pull request contributing lots of verification code for different attestation formats. I really wouldn’t have made it this far without the many pull requests that followed from Alex. Thank you Alex and thank you <a href="https://github.com/abergs/fido2-net-lib/pulls?q=is%3Apr+is%3Aclosed">Open Source</a>.</p>
<h2 id="end-notes">End notes</h2>
<p>Before May 2018 I had no idea what FIDO was. I had never had to do bitwise operations before. I didn’t know what Little-Endian was. I had only once before read a RFC spec. It was the OAuth spec and it left me with a headache. I had never worked with public key cryptography and I didn’t know what Elliptic-curve was. <strong>Now I do.</strong></p>
<p>It’s been an adventure and I’m sure more are to come. My purpose with this project was to learn things and ideally play a small role in making the web more secure by creating a well designed, easy to use and well documented open source library for .NET developers.</p>
<p>Feedback is as always welcome.</p>
<p><a href="https://ideasof.andersaberg.com/development/fido2-net-library">FIDO2 - Things I learned by building a FIDO2 server</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on August 19, 2018.</p>
https://ideasof.andersaberg.com/development/reverse-engineering-apple-x-landing-page
https://ideasof.andersaberg.com/development/reverse-engineering-apple-x-landing-page2017-09-12T00:00:00-04:002017-09-12T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>Hello fellow kids & developers! I decided to reverse engineer the Apple iPhone X website to learn how their slick “X” landing page is made. By combining a couple of effects it makes for a very powerful end result.</p>
<blockquote>
<p>Note: It’s similiar to how I did the <a href="https://aviciitruestories.com/">Avicii True Stories</a> effect.</p>
</blockquote>
<h2 id="first-things-first-this-is-what-were-dissecting-today">First things first, this is what we’re dissecting today!</h2>
<p><img src="/images/apple/apple1.gif" alt="Apple iPhone X Effect" /></p>
<p>Let’s break the gif down:<br />
It starts with an X with a smooth moving plasma. Upon scrolling the X transforms into an iPhone and the plasma remains and becomes the screen. Everything slides in and out in motion. Tight.</p>
<h2 id="okey-so-lets-dissect-this-thing">Okey, so let’s dissect this thing.</h2>
<p>Fire up your F12 Devtools (by pressing F12 in Chrome) or by right clicking the X and choosing “Inspect”.</p>
<p><img src="/images/apple/pic1.png" alt="Inspect" /></p>
<p>When peeking at the DOM you will soon see the 3 parts that make up this magic!</p>
<ol>
<li>One <code class="language-plaintext highlighter-rouge"><canvas></code></li>
<li>One <code class="language-plaintext highlighter-rouge"><div class="hardware-container"></code></li>
<li>Inside the hardware div - One <code class="language-plaintext highlighter-rouge"><video src="nice moving plasma.mp4"/></code></li>
<li>Also inside the hardware div - A second <code class="language-plaintext highlighter-rouge"><canvas /></code></li>
</ol>
<p>So let’s take a look at these building blocks…</p>
<h3 id="the-video">The Video</h3>
<p>Okay, so the animation is simply a movie of the plasma. No iPhone, no “X”. Okay, so that’s nice. No black magic.</p>
<p>Here’s the movie <a href="https://images.apple.com/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/overview/primary/hero/large_2x.mp4">source</a>.</p>
<p><img src="/images/apple/pic2.png" alt="Movie" /></p>
<h3 id="the-iphone-hardware">The iPhone hardware</h3>
<p>So how does the plasma end up being the screen of an iphone?
It’s all about adding layers in the right order.</p>
<p>A rule of thumb is that the browser renders HTML elements from the top-down, so if two html elements are competing for the same area on the screen, the second one would be the one we see (just like putting papers on top of each other). Apple’s webdevs uses this to their advantage, much like you could with layering paper cut outs on each other:</p>
<p><img src="/images/apple/pic3.jpg" alt="Paper cut outs" /></p>
<p>So they place a canvas over the plasma video. The content of the canvas is a <a href="https://images.apple.com/v/iphone-x/a/images/overview/primary/hero_premiere_hardware_large.png">transparent iphone</a> with the rounded corners filled with white to avoid the video sticking out. Since the screen part of the iphone is transparent, whatever element is behind it (the plasma video) will be visible there.</p>
<p><img src="/images/apple/pic4.png" alt="iPhone hardware" /></p>
<p>You can see this effect in action by adding for example a background color and a border to the canvas:</p>
<p><img src="/images/apple/pic5.png" alt="iPhone hardware with background" /></p>
<h3 id="how-about-that-x-cut-out">How about that X cut out?</h3>
<p>Well, we already learned how to do this right? Let’s look at that first canvas element.
It sits above the harware-container <code class="language-plaintext highlighter-rouge"><div></code> in the DOM, so the effect we just learned by layering stuff shouldn’t work -right? RIGHT!?</p>
<p>Well - unlike paper cut outs - in HTML we can bend the natural laws (that’s a lot harder to do with paper cut outs, if you figure out how to do that, please tell NASA).</p>
<p>It’s really easy to tell the browser that element A should be considered on top (or under) element B. This is done using the <code class="language-plaintext highlighter-rouge">z-index</code> property. If we inspect the first canvas parent div, we discover that this is exactly what has been done:</p>
<p><img src="/images/apple/pic6.png" alt="Z-index" /></p>
<p>The higher an elements z-index is, the closer to the eye (more on top) it will be. If we change the value from 2 to 1, the X disappears behind the iphone hardware container div.</p>
<p>The transparent X cutout itself could be a PNG, but in Apples case they’ve used the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes">canvas draw api</a> to simply draw a couple of lines. I’ll leave this as a homework for you to do.</p>
<h3 id="but-everything-is-moving">But everything is moving</h3>
<p>And here we are - at the end with only the final puzzle piece. It all moves syncrhonized while the user is scrolling.</p>
<p>This is done by listening to the <a href="https://developer.mozilla.org/en-US/docs/Web/Events/scroll">scroll event</a> and adjusting the CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform?v=b">animation property</a> <code class="language-plaintext highlighter-rouge">transform</code>.</p>
<p>On scroll, we animate the X and the hardware-container with by changing the <code class="language-plaintext highlighter-rouge">scale</code> function and we make sure the iPhone is in the right position by also adjusting the <code class="language-plaintext highlighter-rouge">translate</code> value. You can inspect this in your browser and see the values change while scrolling:</p>
<p><img src="/images/apple/apple2.gif" alt="CSS3 animations" /></p>
<h3 id="thanks-for-reading">Thanks for reading</h3>
<p>Thanks for reading, hope you liked it! You can ping me on twitter (<a href="http://twitter.com/andersaberg">@andersaberg</a>) with comments, it will make me smile!</p>
<p>If you liked this you might want to try reverse engineering the similiar effect i made for <a href="https://aviciitruestories.com/">Avicii True Stories</a>. You could also have a look at the css animations at <a href="http://listentothe.cloud/">Listen to the clouds</a>.</p>
<p>Please take care of youself and keep on learning!</p>
<p>/A</p>
<p><a href="https://ideasof.andersaberg.com/development/reverse-engineering-apple-x-landing-page">Reverse engineering the Apple iPhone X landing page</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on September 12, 2017.</p>
https://ideasof.andersaberg.com/development/localized-routes-in-asp-net-mvc-core
https://ideasof.andersaberg.com/development/localized-routes-in-asp-net-mvc-core2016-09-06T00:00:00-04:002016-09-06T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>TL;DR: <a href="https://github.com/abergs/LocalizedRoutes">LocalizedRoutes on GitHub</a></p>
<p>I am currently developing a project where I need to localized and theme my routes. Acutally the same website will every only run on language, but the same code will run multiple websites, so I needed to be able to cusomtize my routes. Since there were very little available online for this, I created a library and open sourced it on <a href="https://github.com/abergs/LocalizedRoutes">Github</a>.</p>
<p>Would be cool to write a longer blog post detailing how it works, but knowing my self thah won’t probably happen.</p>
<p>Anyway, if you are looking for how to localize your routes in asp.net core or maybe (as me) just want to theme the urls, definitely take a look at <a href="https://github.com/abergs/LocalizedRoutes">LocalizedRoute</a> for ASP.NET MVC Core 1.0, available as NuGet as well!</p>
<p><a href="https://ideasof.andersaberg.com/development/localized-routes-in-asp-net-mvc-core">Localized Routes in ASP.NET MVC Core 1.0</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on September 06, 2016.</p>
https://ideasof.andersaberg.com/development/tips-for-bash-on-ubuntu-on-windows
https://ideasof.andersaberg.com/development/tips-for-bash-on-ubuntu-on-windows2016-08-17T00:00:00-04:002016-08-17T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>Shoutout to this repo: <a href="https://github.com/abergs/ubuntuonwindows">abergs/ubuntuonwindows</a>. It contains a a collaborative list of good things to know and setup when you run Bash on your Windows 10 machine (Which is natively available since the Windows Anniversary Update).</p>
<p>It covers everything from setting up SSH to how to access your files on your windows drive and vice versa. It’s awesome.</p>
<p>Have a good one! /Anders</p>
<p><a href="https://ideasof.andersaberg.com/development/tips-for-bash-on-ubuntu-on-windows">Tips and Trix for Bash On Ubuntu On Windows</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on August 17, 2016.</p>
https://ideasof.andersaberg.com/development/style-your-styles
https://ideasof.andersaberg.com/development/style-your-styles2015-11-04T00:00:00-05:002015-11-04T00:00:00-05:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>So there is a cool feature in HTML5 and it’s called <code class="language-plaintext highlighter-rouge">contenteditable</code>. It’s a attribute you can place on any element in your page and it will allow your users to change the text inside. This can be useful for a lot of things, but what is even more interesting is the silly things you can do with it.</p>
<h1 id="show-a-styletag-in-the-body">Show a styletag in the body</h1>
<p>Before diving deeper into contenteditable, there is another weird thing you can do in css.
You can render your <code class="language-plaintext highlighter-rouge"><style></code> tag as a normal element. Let me back up. Normally a style tag is located in the <code class="language-plaintext highlighter-rouge"><head></code> with <code class="language-plaintext highlighter-rouge">display:none</code>. Although not recommended, there is nothing stopping you from changing this to something whackier:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><body>
<style class="hipster-style">
.hipster-style {
display:block;
}
</style>
</body>
</code></pre></div></div>
<p>Yep, that’s weird. You can actually render the style tag in the page. But that’s not super helpful. Sure, you can see the styles that are applied. But wouldn’t it be kind of cool if we could do live edits without using the F12 developer tools?</p>
<h2 id="lets-abuse-html">Let’s abuse HTML</h2>
<p>It’s time to introduce the <code class="language-plaintext highlighter-rouge">contenteditable</code>-attribute again. Let’s see what happens when you put contenteditable on your styletag.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><body>
<style class="hipster-style" contenteditable>
.hipster-style {
display:block;
}
body {
background: honeydew;
}
</style>
<h1>Go ahead - change the background color for this page..</h1>
</body>
</code></pre></div></div>
<p>Well - you can live edit the css for your site.</p>
<h2 id="time-to-style-the-styles">Time to style the styles</h2>
<p>So I think you know where this is going. Thanks to the amazeballs of HTML5 you can live edit the styles of your styles.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><body>
<style class="hipster-style" contenteditable>
.hipster-style {
display:block;
background: tomato;
color:white;
}
body {
background: honeydew;
}
</style>
<h1>Go ahead - change the background color for this page..</h1>
</body>
</code></pre></div></div>
<p>Try it out live: <a href="http://codepen.io/anon/pen/avjgzE">http://codepen.io/anon/pen/avjgzE</a></p>
<p>Yep, that’s meta. HTML5 and CSS allows you to style the CSS itself. Well, kind of - CSS allows you to style elments on your page, right? And the <code class="language-plaintext highlighter-rouge"><style></code>-tag is an element in your page, as long as you put in the <code class="language-plaintext highlighter-rouge"><body></code> instead of <code class="language-plaintext highlighter-rouge"><head></code>.</p>
<h2 id="is-this-useful">Is this useful?</h2>
<p>No. And Yes. This is not something you would throw into your every-day site. But it’s kind of cool things you could do with this.</p>
<p>For example it’s a great way to let the visitor edit the styles of an CSS example and see what makes what happen - without needing to use a third party sandbox such as JSFiddle or CodePen, or doing a lot of “onChange update this JS mess” etc.</p>
<p>Happy hipster hacking!</p>
<p><a href="https://ideasof.andersaberg.com/development/style-your-styles">The fastlane to css hipster heaven</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on November 04, 2015.</p>
https://ideasof.andersaberg.com/personal/the-next-chapter-looking-back
https://ideasof.andersaberg.com/personal/the-next-chapter-looking-back2015-08-10T00:00:00-04:002015-08-10T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<blockquote>
<p>The future will in one way or another always become the present. Question is - will you chase it?</p>
</blockquote>
<p>I’m writing this blog post on board the commuter train between Stockholm and Uppsala. After five years at Caspeco, I now take the next step in my journey. For the next 1½ year, I will attend the <em>Digital Data Strategist</em> program at <strong><a href="http://hyperisland.se">Hyper Island</a></strong>.</p>
<p>I started working for Caspeco as a teenager. I was 19 years old and a junior developer when I wrote my first line of code at Caspeco. My experience with programming were limited to HTML, CSS, some jQuery and simple PHP webpages. Since then I’ve had the amazing opportunity to delve into all sorts of corners in the wonderful world of code and programming.</p>
<p>From building <em>monolithic desktop applications, simple todo-systems, advanced todo-systems, installing our Continuous-Integration Server, optimizing MySQL databases, building Single Page Applications, Web widgets,</em> exploring <em>Microsoft Azure</em>, hacking on <em>Raspberry PI’s</em> and a thousand things more … and finally for the last one and a half years, taking on the responsibility to move Caspeco away from its roots in the Windows Desktop market and deploying us on all devices fitted with a web browser; designing and building both the front end platform as well as the server infrastructure and the backend API’s.</p>
<p><em>Wow</em>. Reading the list of things we’ve done at Caspeco makes me proud. Proud of myself for pushing us forward. Proud of Caspeco for supporting me.</p>
<p>If I were to choose one thing that’s important for me with my employer, it would be <em>to be given the opportunity to learn</em>. And boy, did I learn a lot at Caspeco.</p>
<p>Thank you Caspeco - until I see you again,<br />
Anders.</p>
<p><a href="https://ideasof.andersaberg.com/personal/the-next-chapter-looking-back">The next chapter: Looking back</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on August 10, 2015.</p>
https://ideasof.andersaberg.com/development/email-service
https://ideasof.andersaberg.com/development/email-service2015-08-09T00:00:00-04:002015-08-09T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>One of my last tasks at Caspeco were to build our email service for our new platform. Normally, this could be as simple as <code class="language-plaintext highlighter-rouge">Smtp.Send(email)</code> but I wanted to improve the value that it generated so, I really took a deeper approach. First, let me tell you have we do it in our old platform.</p>
<h2 id="how-we-used-to-do">How we used to do</h2>
<p>We already make use of a third party emailservice, instead of our own SMTP service. This gives us the benefit of lower spam ratings, as well as some simple statistics, such as email sent, email bounced etc. But we sent the mails synchronous, causing long response times for sending many emails, especially since we often send attachments (payslip pdf’s, schedule printouts etc).</p>
<h2 id="value-proposition-for-v2">Value proposition for v2</h2>
<p>When building the new service, the following were the goals and value propositions I took into account.</p>
<ul>
<li><strong>Faster</strong>. Allow background processing for faster response times.</li>
<li><strong>More resilient</strong>. Do not fail completely if Postmark is down. Allow retries and corrections for historic emails.</li>
<li><strong>Better Insights</strong>. Ability to see who received their email and who didn’t, for example when sending salary specifications. Ability to add custom tags.</li>
<li><strong>Multi platform</strong>. Ability to render Messages not only in the email client, but inside our apps.</li>
<li>Support <strong>large attachments</strong> and, again, attachments for in-app purposes.</li>
<li><strong>Better API</strong> for developers, current API caused weirdness in some use cases.</li>
</ul>
<h2 id="the-design">The design</h2>
<ol>
<li>
<p>IEmailer
Add an email to the IEmailLog, Upload and reference any attachments, schedule it for sending<br />
<em>Current implementation simply calls subsystems in the correct order and with the correct data.</em></p>
</li>
<li>
<p>IEmailLog
Represents a log of all emails (pending, sent, failed) and their related events.<br />
<em>Current implementation stores information in SQL database.</em></p>
</li>
<li>
<p>IFiles
Service to Store, Retrieve and reference stored/uploaded files.<br />
<em>Current implementation leverages azure BLOB storage.</em></p>
</li>
<li>
<p>IEmailCommands
Commands that is used to trigger actions, such as sending an email from the emaillog.<br />
<em>Current implementation leverages azure storage Queues. This subsystem exist because we have multiple EmailWorkers and we need to make sure we have atomic processing of an email message (no concurrency/race conditions)</em></p>
</li>
<li>
<p>EmailWorker
The background process that process Commands from IEmailCommands.
Responsible for:</p>
<ul>
<li>Acting upon Commands</li>
<li>Parse Email messages to Text & Html layouts</li>
<li>Delivery through either Postmark or other providers.</li>
<li>Current implementation is a background process running on the api server itself. This implies that we have multiple email workers running at the same time, giving both resilience but also some side effects such as concurrency.</li>
</ul>
</li>
</ol>
<h2 id="reflection">Reflection</h2>
<p>Although the new platform adds a certain complexity since it involves more components, every component is well defined and only responsible for one thing. Also the goals defined for version 2 puts certain requirements that cannot be worked around, such as attachment storage and rendering an email in the application.</p>
<p>The API for sending emails remain simple:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_emailer.Send(new Email {
to: "anders@example.se",
title: "Hello!"
});
</code></pre></div></div>
<p>On could argue that the biggest difference and sacrifice is the lack of a synchrounos result, but I would argue that isn’t true, since the reply we receive from Postmark is not a definitive reply, just that everything checkouts on their end. If postmark returned OK and the email later bounced, we would be lying about the result.</p>
<p>How do you do Email? Would you do it differently? Tell me in the comments.</p>
<p><a href="https://ideasof.andersaberg.com/development/email-service">Building the Email Service that provides value for Caspeco</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on August 09, 2015.</p>
https://ideasof.andersaberg.com/development/Owin-Friendly-Exceptions-Web-Api
https://ideasof.andersaberg.com/development/Owin-Friendly-Exceptions-Web-Api2015-03-09T00:00:00-04:002015-03-09T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>The team at Caspeco needed a way to translate exceptions to nice HTTP responses. The exceptions could be thrown by a custom middleware, within the Web Api framework or in our bussiness logic.</p>
<p>So we built a thin Owin Middleware to translate all exceptions into nice HTTP responses that makes sense when you read them in the client consuming your api. The result is available as the nuget package <code class="language-plaintext highlighter-rouge">OwinFriendlyExceptions</code> and the source is available at Github: <a href="https://github.com/abergs/OwinFriendlyExceptions">abergs/OwinFriendlyExceptions</a></p>
<p><a href="https://ideasof.andersaberg.com/development/Owin-Friendly-Exceptions-Web-Api">Owin Friendly Exceptions from Web Api</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on March 09, 2015.</p>
https://ideasof.andersaberg.com/development/fast-css3-animation-without-stuttering
https://ideasof.andersaberg.com/development/fast-css3-animation-without-stuttering2014-08-11T00:00:00-04:002014-08-11T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>I was tasked with a very simple task. Moving a ball from the left edge of the screen to the right edge. That’s a simple task, and have been possible for ages using for example jQuery animations. However, the ball had to move <strong>smoothly</strong> over the screen and back in 0.5 seconds.</p>
<h2 id="the-problem-with-smoothly">The problem with smoothly</h2>
<p>Obviously, it has to have a great performance. So I decided to instead of manipulating the DOM with javascript, I would use css3 animations to translate the position of a div. The thing is, the speed the ball was moving at is too high for the human eye to resolve smoothly. We experience a trace or shadow copies of the figure making the ball dizzy.</p>
<h2 id="the-solution">The solution</h2>
<p>Add Motion blur. That is the way games do it and that is the way Hollywood does it. Adding motion blur to an object that is moving fast makes it look smoother to the human eye.</p>
<p>In my case, i could just add a box-shadow or a filter:blur to the balls css class.</p>
<p><a href="https://ideasof.andersaberg.com/development/fast-css3-animation-without-stuttering">Fast css3 animations without stuttering</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on August 11, 2014.</p>
https://ideasof.andersaberg.com/development/Claims-Based-HTTP-Security
https://ideasof.andersaberg.com/development/Claims-Based-HTTP-Security2014-06-06T00:00:00-04:002014-06-06T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>I currently run a couple of projects where we need to implement a Security Service for Authenticating and Authorization users.</p>
<p>Two videos by Dominick of ThinkTecture fame:</p>
<h2 id="what-and-why-is-claims-and-tokens">What and Why is Claims and Tokens</h2>
<iframe src="//player.vimeo.com/video/43549130" width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""> </iframe>
<p><a href="http://vimeo.com/43549130">Dominick Baier - Authentication & Authorization in .NET 4.5 - Claims & Tokens become the standard Model</a> from <a href="http://vimeo.com/ndcoslo">NDC Conferences</a> on <a href="https://vimeo.com">Vimeo</a>.</p>
<h2 id="securing-aspnet-web-apis-and-http-services">Securing ASP.NET Web APIs and HTTP Services</h2>
<iframe src="//player.vimeo.com/video/68327244" width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""> </iframe>
<p><a href="http://vimeo.com/68327244">Dominick Baier: Securing ASP.NET Web APIs and HTTP Services</a> from <a href="http://vimeo.com/ndcoslo">NDC Conferences</a> on <a href="https://vimeo.com">Vimeo</a>.</p>
<p><a href="https://ideasof.andersaberg.com/development/Claims-Based-HTTP-Security">Claims based HTTP security</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on June 06, 2014.</p>
https://ideasof.andersaberg.com/development/Introduction-To-Orleans
https://ideasof.andersaberg.com/development/Introduction-To-Orleans2014-06-05T00:00:00-04:002014-06-05T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>In this article I want to give you a short introduction to <a href="http://orleans.codeplex.com">Project Orleans</a>.</p>
<h2 id="what-is-project-orleans">What is Project Orleans?</h2>
<p>Orleans is a product from the eXtreme Computing division at Microsoft Research. It aims to help developers to develop “Cloud Native” services and it is a combination of <strong>theories</strong> of how you build large distributed applications plus the <strong>tooling and framework</strong> to make it very simple in practice.</p>
<blockquote>
<p>“Orleans targets developers who are not distributed system experts, although our expert customers have found it attractive too” — Orleans Team</p>
</blockquote>
<h2 id="what-is-it-really">What is it really?</h2>
<p>Okay, so you <em>kind of</em> know what Orleans is now. It’s some framework and methodology to create mongo bongulos huge systems, like the <a href="http://channel9.msdn.com/Events/Build/2014/3-641">Halo 4 Presence</a>.
But why should <em>I</em> care?</p>
<p>Think of almost every time you are processing data and you use a <code class="language-plaintext highlighter-rouge">SyncLock</code>. Or any time you are really thinking of the impact if this code/query would be executed conccurrent while it’s still progressing. Or if you just have a ton of work that needs to run concurrent. Like say parsing incoming messages and in realtime notify other parts of your system.</p>
<h2 id="say-hello-to-orleansigrain---your-new-best-friend">Say hello to Orleans.IGrain - your new best friend</h2>
<p>It’s also known as a .NET Class and Interface.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class PersonGrain : BaseGrain, IGrain { ... }
</code></pre></div></div>
<p>In the <a href="http://orleans.codeplex.com/wikipage?title=Getting%20Started%20with%20Orleans&referringTitle=Orleans%20Documentation">theories presented by Orleans</a>, you will read a lot about the <a href="http://orleans.codeplex.com/wikipage?title=Core%20Concepts&referringTitle=Getting%20Started%20with%20Orleans">Actor Model</a>. The Actor model share some of it ideas with the object oriented, self encapsulated model.</p>
<blockquote>
<p>“The units of distribution, actors, encapsulating data and computation, are called grains.”</p>
</blockquote>
<p>So in Orleans, a grain is just a C# class. That you can fetch by:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var person = await PersonGrainFactory.GetPerson(id);
</code></pre></div></div>
<p>And then you can call functions on that person:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var report = await person.ComputeYourExpenseReportFromYourStoredData();
await person.UpdateYourManagerWithNewExpenseReport(report);
</code></pre></div></div>
<p>Here is the interesting part. The Class Person - do not live where you write this code. This code might be in your MVC-Controller or WebForm BtnClick handler, but <code class="language-plaintext highlighter-rouge">ComputeYourExpenseReport</code> is actually running on another server. It could be running on 1 of your 100 servers. You wouldn’t care. It’s just running. <strong>It’s just ORLEANS</strong>. You don’t have to know where to execute this, or if it’s already running if anyone else have already requested this.</p>
<p><strong>A Grain is single threaded</strong>. That means that even if multiple web requests asks to compute that report it will be done sequentially, avoiding concurrency lockings and other very problematic situations.</p>
<h2 id="you-still-cant-explain-orleans-to-your-friend-right">You still can’t explain Orleans to your friend, right?</h2>
<p>Think of it like this:</p>
<blockquote>
<p>A Grain is somewhat like a cache with behaviour, which is <em>always available</em> as if it would be in memory and you do not have to worry about concurrency</p>
</blockquote>
<p>It looks liks it’s just calling some function on some class that is already instantiated in managed memory with data — but in fact it’s running code on another machine in a cluster which handles it’s own state and concurrency.</p>
<p>So Why Orleans? Because if you are not a well faired distributed system expert, building a <em>distributed</em>, <em>scaleable</em> or <em>concurrent</em> system is quite hard. And it is <em>very</em> hard when you combine all three words at the same time.</p>
<p>Follow the white rabbit.</p>
<ul>
<li><a href="http://orleans.codeplex.com/">Orleans Samples</a></li>
<li><a href="http://orleans.codeplex.com/wikipage?title=Samples%20Overview&referringTitle=Documentation">Orleans Samples Overview</a></li>
<li><a href="http://orleans.codeplex.com/wikipage?title=Getting%20Started%20with%20Orleans&referringTitle=Orleans%20Documentation">Getting Started With Orleans</a></li>
<li><a href="https://channel9.msdn.com/Events/Build/2014/3-641">Build 2014 Presentation</a></li>
<li><a href="http://channel9.msdn.com/Shows/Going+Deep/Project-Orleans-A-Cloud-Computing-Framework">Project Orleans explained by the Orleans team</a></li>
<li><a href="http://research.microsoft.com/pubs/141999/pldi%2011%20submission%20public.pdf">Orleans: A Framework for Cloud Computing</a></li>
<li><a href="http://orleans.codeplex.com/wikipage?title=Articles&referringTitle=Documentation">More Articles about Orleans…</a></li>
</ul>
<p>Expect more articles on Orleans from me in the near future.</p>
<p><a href="https://ideasof.andersaberg.com/development/Introduction-To-Orleans">Introduction To Project Orleans</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on June 05, 2014.</p>
https://ideasof.andersaberg.com/personal/Finished-Moving-My-Old-blog
https://ideasof.andersaberg.com/personal/Finished-Moving-My-Old-blog2014-06-02T00:00:00-04:002014-06-02T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>I just finished moving all of my old posts!</p>
<p>…complete with disqus comments by using disqus URL mapper.</p>
<p>Now the azure site is going down and i’ll gain a couple of bucks a month.</p>
<p><a href="https://ideasof.andersaberg.com/personal/Finished-Moving-My-Old-blog">I Finished moving my old posts!</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on June 02, 2014.</p>
https://ideasof.andersaberg.com/personal/I-Moved-My-Blog
https://ideasof.andersaberg.com/personal/I-Moved-My-Blog2014-06-01T00:00:00-04:002014-06-01T00:00:00-04:00Anders Åberghttps://ideasof.andersaberg.comanders@andersaberg.com<p>My short story about saving a couple of bucks a month and how I switched blogging platform.</p>
<p>I’m starting a company together with my best friend, Jacob. I’m currently employed at <a href="http://caspeco.se">Caspeco</a> and for a while I will only work 60% while working on my startup 40% of normal working hours. As you understand, this puts my salary off by 40%. On top of this I doubled my rent expense due to my (now ex-)girlfriend moving out.</p>
<p>So - long story short; my budget is quite low. I spend about $22 on Azure every month. Not much, but in my situation, defintely something I’d rather spend on food!</p>
<p>The only thing I have running on Azure is my blog, which certainly could be hosted elsewhere. It is currently running my own blogging platform based on SQL.</p>
<p>I looked through some of the popular alternatives but I want to <em>own</em> my data. That leaves wordpress.com, blogger.com out of the picture. Ghost is getting some hype but it requries a hosted verison of Node to run. Not ideal.</p>
<p>I have looked on Github Pages earlier for the ability to run blogs, but Jekyll has earlier put me off. This time I decided to try it out.</p>
<p>Starting off with browsing for a how to guide, I found a guide together with the template I am using: <a href="https://github.com/hmfaysal/hmfaysal-omega-theme">https://github.com/hmfaysal/hmfaysal-omega-theme</a>. The instrucitons for running Jekyll on windows is quite bad, but here is a better guide to <a href="http://chrismeserole.com/coding/install-ruby-rails-jekyll-on-windows/">Jekyll On Windows</a></p>
<p>I like the theme and since most things “just work” this is a good starting spot.</p>
<p>I will migrate my earlier posts manually in the following week. Have patience! :)</p>
<p><a href="https://ideasof.andersaberg.com/personal/I-Moved-My-Blog">I Moved my blog to Github Pages</a> was originally published by Anders Åberg at <a href="https://ideasof.andersaberg.com">Ideas Of Anders Åberg</a> on June 01, 2014.</p>