<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Scott Alexander]]></title><description><![CDATA[Thoughts, projects and ideas.]]></description><link>http://tech.scottalex.com/</link><image><url>http://tech.scottalex.com/favicon.png</url><title>Scott Alexander</title><link>http://tech.scottalex.com/</link></image><generator>Ghost 3.42</generator><lastBuildDate>Sat, 30 May 2026 09:16:10 GMT</lastBuildDate><atom:link href="http://tech.scottalex.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[AI Introduction: Resources for Modern AI]]></title><description><![CDATA[<p>We all know the AI landscape is moving fast. Building a solid foundation requires more than just reading headlines—it demands hands-on experience and a guided approach to the core concepts.<br><br>For some context, I’m no stranger to Machine Learning, I remember building my first Neural Network to predict</p>]]></description><link>http://tech.scottalex.com/resources-for-modern-ai/</link><guid isPermaLink="false">698f267df9ebf1000178ead5</guid><category><![CDATA[AI]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Fri, 13 Feb 2026 14:06:22 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1684369175809-f9642140a1bd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM5fHxhaXxlbnwwfHx8fDE3NzA5NTI2NzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1684369175809-f9642140a1bd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM5fHxhaXxlbnwwfHx8fDE3NzA5NTI2NzR8MA&ixlib=rb-4.1.0&q=80&w=2000" alt="AI Introduction: Resources for Modern AI"><p>We all know the AI landscape is moving fast. Building a solid foundation requires more than just reading headlines—it demands hands-on experience and a guided approach to the core concepts.<br><br>For some context, I’m no stranger to Machine Learning, I remember building my first Neural Network to predict weather back at university (with some success) and even attempted to classify clinical coding data prior to modern advancements in the field. I’m very aware that due to the clients I work with my knowledge is a bit historic and my hands on experience has been limited.</p><p>To tackle this, I recently dedicated a couple of learning days to diving deep into modern AI principles. My goal was simple: build foundational AI knowledge, get hands-on experience with multiple tools (like Claude, Gemini, and CoPilot), and ultimately, produce a set of resources to help others get started.<br><br>What follows is the curated list of resources and topics that formed the backbone of this deep dive. <br><br><br><br><strong>1. Getting Started: Foundational Knowledge</strong><br><br>For a solid introduction, I began with a course on <strong>Agentic AI</strong>—a great entry point for anyone new to modern AI concepts. It provides a good overview of the topic with practical, non-coding-based examples.</p><ul><li><strong>Course:</strong> <a href="https://app.pluralsight.com/ilx/video-courses/agentic-ai-developers/course-overview">Agentic AI for Developers</a> (Pluralsight)</li></ul><p>I’m very aware that not everyone uses Pluralsight, as such here is a non subscription alternative from Udermy that covers similar topics. </p><ul><li><strong>Course: </strong><a href="https://www.udemy.com/course/intro-to-ai-agents-and-agentic-ai/?srsltid=AfmBOooWRKuVhlx1D2_GhttrZ5ftzUSjOJD8RHK110Pd40V0todxbynr">Intro to AI Agents and Agentic AI</a> (Udemy)<br><br></li></ul><p><strong>2. Essential Modern AI Concepts</strong><br><br>The following topics have been highlighted as crucial areas for any AI enthusiast or developer to understand. I've compiled the video resources that I've found useful and alternative text references for those that learn better from reading.<br><br>  <br><strong>Model Context Protocol: </strong>An open API standard for connecting to components in an AI system.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=eur8dUO9mvE">How to build a model context protocol</a></li><li>Text Resources: <a href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol: Getting Started</a><br></li></ul><p><strong>Vector Databases: </strong>Specialised databases used to store and manage vector embeddings for efficient similarity search.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=gl1r1XV0SLw">What is a Vector Database?</a></li><li>Text Resources:  <a href="https://www.pinecone.io/learn/vector-database/">What is a Vector Database?</a><br></li></ul><p><strong>RAG (Retrieval-Augmented Generation):</strong> A technique linked with vector indices that improves model output by retrieving facts from external knowledge bases.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=T-D1OfcDW1M&amp;t=18s">What is RAG?</a></li><li>Text Resources: <a href="https://aws.amazon.com/what-is/retrieval-augmented-generation/">What is Retrieval-Augmented Generation?</a><br></li></ul><p><strong>Prompt Patterns:</strong> Structured approaches and best practices for engineering effective prompts to guide an AI model.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=_fvjcWPRNVs&amp;list=PLNukArGE4sTDSuVwa6VLvKHlkLQHxDmxV">Prompt Patterns Playlist</a></li><li>Text Resources: <a href="https://medium.com/@corraljrmiguel/21-prompt-patterns-you-should-know-636c931bba2a">21 Prompt Patterns You Should Know</a><br></li></ul><p><strong>Tool Use / Function Calling:</strong> The ability of an LLM to identify and execute external functions or tools to complete a request.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=h8gMhXYAv1k">Tool calling for LLMs</a>, <a href="https://www.youtube.com/watch?v=Qor2VZoBib0">OpenAI function calling</a></li><li>Text Resources: <a href="https://developers.openai.com/api/docs/guides/function-calling">Function Calling Guide</a><br></li></ul><p><strong>Embeddings:</strong> Vector representations of text, images, or other data that capture their semantic meaning.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=wgfSDrqYMJ4">Turning words into vectors</a>, <a href="https://www.youtube.com/watch?v=my5wFNQpFO0">Embeddings</a></li><li>Text Resources: <a href="https://www.cloudflare.com/en-gb/learning/ai/what-are-embeddings/">What are Embeddings?</a><br></li></ul><p><strong>Context Limits / Tokenisation:</strong> Understanding the maximum amount of input an LLM can process and how text is broken down into tokens.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=-uW5-TaVXu4">Most devs don't understand how Context Windows work</a>, <a href="https://www.youtube.com/watch?v=nKSk_TiR8YA">Most Devs don't know how AI Tokens work</a></li><li>Text Resources: <a href="https://www.ibm.com/think/topics/context-window">The Context Window</a>, <a href="https://medium.com/data-science-collective/the-invisible-building-blocks-of-ai-what-you_need_to_know_about_tokenization_acadd86a63ba">What is Tokenization?</a><br></li></ul><p><strong>Guardrails and Safety Layers:</strong> Mechanisms and policies implemented to ensure AI systems operate safely and ethically.</p><ul><li>Videos: <a href="https://www.youtube.com/watch?v=ncbo3bSGPI0">AI Guardrails</a>, <a href="https://www.youtube.com/watch?v=4QXtObc61Lw">Security and AI Governance</a></li><li>Text Resources: <a href="https://www.mckinsey.com/featured-insights/mckinsey-explainers/what-are-ai-guardrails">What are AI Guardrails?</a>, <a href="https://medium.com/data-science/a-new-approach-to-ai-safety-layer-enhanced-classification-lec-56141aa0f6be">AI Safety Layer Enhanced Classification</a></li></ul><p></p><p><strong>Next Steps</strong><br><br>Building knowledge is just the first step. The true learning comes from hands-on work. My next steps involve getting Claude up on running on my local machine and diving into developing a tool idea for career monitoring—a tool that I’ve been wanting to develop for some time. </p><p>If you're on your own AI learning journey, I hope this curated list gives you some solid resources to understand the fundamentals. Happy learning!</p><p><br></p>]]></content:encoded></item><item><title><![CDATA[The Quest for Identity Part 3: APIs]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Now for the final part of the Quest for Identity posts, how to configure an API to work with Identity Server 4.</p>
<p><strong>RESTful Service configuration</strong></p>
<p>Firstly let's create a new ASP.Net Web application project within our solution. Make sure it's a created with the Web API template so it</p>]]></description><link>http://tech.scottalex.com/the-quest-for-identity-part-3-apis/</link><guid isPermaLink="false">5c83b5653b595e0001a9d995</guid><category><![CDATA[Identity Server 4]]></category><category><![CDATA[C#]]></category><category><![CDATA[.Net Core 2]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Fri, 01 Jun 2018 10:57:14 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/06/document-driver-s-license-driving-licence-45113--1-.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/06/document-driver-s-license-driving-licence-45113--1-.jpg" alt="The Quest for Identity Part 3: APIs"><p>Now for the final part of the Quest for Identity posts, how to configure an API to work with Identity Server 4.</p>
<p><strong>RESTful Service configuration</strong></p>
<p>Firstly let's create a new ASP.Net Web application project within our solution. Make sure it's a created with the Web API template so it can act as a RESTful service. I've named mine IS4REST. So how can we now implement IS4 Authentication within our project?</p>
<p>Surprisingly this is the easiest stage of the IS4 configuration process. Within the Startup class in ConfigureServices(), add in the following CORS configuration and authentication with your own port configurations:</p>
<pre><code>services.AddMvc();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddCors(options =&gt;
{
    options.AddPolicy(&quot;default&quot;, policy =&gt;
    {
        policy.AllowAnyHeader()
            .AllowAnyMethod()
            .AllowAnyOrigin();
    });
});

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =&gt;
    {
        // base-address of your identityserver
        options.Authority = &quot;https://localhost:44392/&quot;;

        // name of the API resource
        options.Audience = &quot;https://localhost:44392/resources&quot;;
    });
</code></pre>
<p>Then enable them in the Cofiguration() call below:</p>
<pre><code> app.UseCors(&quot;default&quot;);
 app.UseAuthentication();
</code></pre>
<p>Finally, since this is a new project, our Identity Server needs to know about this as a client setting. Add the following to the clients' section of the Identity Server 4 Config file:</p>
<pre><code>new Client 
{
    ClientId = &quot;rest&quot;,
    ClientName = &quot;REST API&quot;,
    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

    RequireConsent = false,
    AllowAccessTokensViaBrowser = true,
    ClientSecrets =
    {
        new Secret(&quot;secret&quot;.Sha256())
    },

    RedirectUris = {&quot;https://localhost:44305/signin-oidc&quot;},
    PostLogoutRedirectUris = {&quot;https://localhost:44305/signout-callback-oidc&quot;},

    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        &quot;api1&quot;
    },
    AllowOfflineAccess = true
}
</code></pre>
<p>This is exactly the same as our MVC configuration except it has a different ID and we are Allowing Access Tokens through a Browser. That's all we need to do this side, over to the Angular set up using the project from the previous post.</p>
<p><strong>Angular Client Set Up</strong></p>
<p>We're going to need a new page and service to call our new API Service run the following:</p>
<pre><code>ng g c components/api-test
ng g s shared/services/api-test
</code></pre>
<p>Register the component in the app module and the service in the shared providers. Then we need to fill them in with the following:</p>
<pre><code>#api-test-component.ts
import { Component, OnInit } from '@angular/core';
import { ApiTestService } from '../../shared/services/api-test.service';

@Component({
    selector: 'ac-api-test',
    templateUrl: './api-test.component.html',
    styleUrls: ['./api-test.component.css']
})
export class ApiTestComponent implements OnInit {

  response: any = null;
  constructor(private _apiTestService: ApiTestService) { }

  ngOnInit() {
      this._apiTestService.GetValues().subscribe(response =&gt; {
      this.response = response;
  });
}

#api-test-component.html
&lt;p&gt;
    api-test works!
&lt;/p&gt;

{{response}}

#api-test-service
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';

@Injectable()
export class ApiTestService {

    apiRoot : string = &quot;https://localhost:44329/api/&quot;

    constructor(private _http : HttpClient) { }

    GetValues(): Observable&lt;any&gt; {
        return this._http.get(this.apiRoot + &quot;values&quot;)
        .do(data =&gt; {
        })
    .catch(this.handleError);
    }

    private handleError(err: HttpErrorResponse) {
        console.log(err.message);
        return Observable.throw(err.message);
    }
}
</code></pre>
<p>Finally to ge tthis into a working state, lets add this new component to the router and provide a link to it on the app.component.html:</p>
<pre><code>#app.component.html
&lt;h3&gt;&lt;a [routerLink]=&quot;['/']&quot;&gt;Home&lt;/a&gt; | &lt;a [routerLink]=&quot;['/protected']&quot;&gt;Protected&lt;/a&gt; | &lt;a [routerLink]=&quot;['/api-test']&quot;&gt;API Test&lt;/a&gt;&lt;/h3&gt;
&lt;h1&gt;
    {{title}}
&lt;/h1&gt;
&lt;router-outlet&gt;&lt;/router-outlet&gt;

#app-route-module.ts
const routes: Routes = [
{
    path: '',
    component: HomeComponent
},
{
    path: 'protected',
    component: ProtectedComponent,
    canActivate: [AuthGuardService]
},
{
    path: 'api-test',
    component: ApiTestComponent,
    canActivate: [AuthGuardService]
},
{
    path: 'auth-callback',
    component: AuthCallbackComponent
}
</code></pre>
<p>Now this will work! We've successfully configured our application to call our new RESTful service. However, there is a but. We don't currently require Authorization on our api/values route. You should enable it with the [Authorize] property on the Values API class and see what happens. As expected a 401. To get it working we need to pass back our Identity Token.</p>
<p><strong>Setting up an Interceptor</strong></p>
<p>Now the best way to pass back our token is to use an interceptor because in general practice you'll be providing the token to multiple API calls. Our intercept will spot all API calls and ad the token to the header before sending it on its way.</p>
<p>Firstly add the following package to our solution.</p>
<pre><code>yarn add angular2-jwt
</code></pre>
<p>Next up let's add some token functionality to our AuthService, they should be quite clear on what they do:</p>
<pre><code>#auth.service.ts

...
public getToken(): string {
    var token = localStorage[0];
    return token;
}

public isAuthenticated(): boolean {
    // get the token
    const token = this.getToken();
    // return a boolean reflecting 
    // whether or not the token is expired
    return tokenNotExpired(null, token);
}

public collectFailedRequest(request): void {
    this.cachedRequests.push(request);
}

public retryFailedRequests(): void {
    // retry the requests. this method can
    // be called after the token is refreshed
}
</code></pre>
<p>With that in place, lets add our interceptors. Create new folder called interceptors and then add two files (manual process I'm afraid), jwt.interceptor.ts and token.interceptor.ts. The code for these two files is as follows:</p>
<pre><code>#jwt.interceptor.ts
import 'rxjs/add/operator/do';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { AuthService } from '../shared/services/auth.service';
import { Observable } from 'rxjs/Observable';

export class JwtInterceptor implements HttpInterceptor {
    constructor(public auth: AuthService) {}
    intercept(request: HttpRequest&lt;any&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; 
    {    
        return next.handle(request).do((event: HttpEvent&lt;any&gt;) =&gt; {
                if (event instanceof HttpResponse) {
                // do stuff with response if you want
                }
            }, 
        (err: any) =&gt; {
            if (err instanceof HttpErrorResponse) {
                if (err.status === 401) {
                    this.auth.collectFailedRequest(request);
                }
            }
        });
    }
}

#token.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { AuthService } from '../shared/services/auth.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
    constructor(public auth: AuthService) {}
    intercept(request: HttpRequest&lt;any&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; {

            request = request.clone({
                setHeaders: {
                Authorization: this.auth.getAuthorizationHeaderValue()
            }
        });
        return next.handle(request);
    }
}
</code></pre>
<p>Have a look through the code and have a look at home it manages to add the Authentication Token to our requests. We have one final thing to do, let's add the interceptor to our app.module as a provider and import the relevant files:</p>
<pre><code>#app.module.ts
Providers : [{
    provide: HTTP_INTERCEPTORS,
    useClass: TokenInterceptor,
    multi: true
}]
</code></pre>
<p>Give it another go with some breakpoints to see what happens but we now have our API working alongside our client. That is where I'll leave these posts now, there are a lot more areas to explore but, I hope the last three posts have provided enough information to help you hit the ground running. Cheers for reading.</p>
<p>The code for this <a href="https://github.com/SRAlexander/IS4-ASPUsers/tree/APIAuth">here</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[HttpClients with Proxies in .Net Core 2]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>One of my biggest day to day problems is dealing with a corporate proxy. Especially with .Net Core 2 which is not completely configurable from the appsettings.json file yet. This post is about how to configure your application to handle it.</p>
<p><strong>Configurable Proxy Settings</strong></p>
<p>Firstly an application will not</p>]]></description><link>http://tech.scottalex.com/net-core-2-with-proxies/</link><guid isPermaLink="false">5c83b5653b595e0001a9d994</guid><category><![CDATA[.Net Core 2]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Thu, 24 May 2018 12:54:16 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/05/vpn-2714263_1920.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/05/vpn-2714263_1920.jpg" alt="HttpClients with Proxies in .Net Core 2"><p>One of my biggest day to day problems is dealing with a corporate proxy. Especially with .Net Core 2 which is not completely configurable from the appsettings.json file yet. This post is about how to configure your application to handle it.</p>
<p><strong>Configurable Proxy Settings</strong></p>
<p>Firstly an application will not always be working behind a proxy so we need to make it configurable. In your appsettings.json file as in the following settings:</p>
<pre><code>&quot;MyConfig&quot;: {
    &quot;UseProxy&quot;: &quot;true&quot;,
    &quot;ProxyHost&quot;: &quot;127.0.0.1&quot;,
    &quot;ProxyPort&quot;: &quot;3128&quot;,
}
</code></pre>
<p>We then need a class for these settings to map to. Create the following MyConfig class:</p>
<pre><code>public class MyConfig
{
    	public bool UseProxy { get; set; }
    	public string ProxyHost { get; set; }
    	public int ProxyPort { get; set; }
	}
</code></pre>
<p>Now we can configure our Startup.cs class to set up our configuration parameters as an injectable. In the ConfigureServices function add the following:</p>
<pre><code>public void ConfigureServices(IServiceCollection services)
    {
	....
	// Add our Config object so it can be injected
        	services.Configure&lt;MyConfig&gt;(Configuration.GetSection(&quot;MyConfig&quot;));
}
</code></pre>
<p>Now we can inject our configuration parameters where ever we need to, using the standard dependency injection style:</p>
<pre><code>private readonly IOptions&lt;MyConfig&gt; _config;

    public HttpProxyClientService(IOptions&lt;MyConfig&gt; config)
    {
	_config = config;
    }
</code></pre>
<p><strong>Create an Injectable Module</strong></p>
<p>Following SOLID principals we will want to create a single point of reference for anything that will need to use the proxy. Two objects that are likely to use it are the HttpClient object and HttpWebRequest object. By designing it as a service you can add your own required objects down the line. For now, create the following interface:</p>
<pre><code>public interface IHttpProxyClientService
{
    	HttpClient CreateHttpClient();
    	HttpWebRequest CreateHttpWebRequest(string requestUri);
 }
</code></pre>
<p>And then this boilerplate class for the next sections to come.</p>
<pre><code>public class HttpProxyClientService : IHttpProxyClientService
	{
    	private readonly IOptions&lt;MyConfig&gt; _config;

    	public HttpProxyClientService(IOptions&lt;MyConfig&gt; config)
    	{
        		_config = config;
    	}

    	public HttpClient CreateHttpClient()
    	{
		return null;
    	}

    	public HttpWebRequest CreateHttpWebRequest(string requestUri)
    	{
		return null;
   		}
	}
</code></pre>
<p>Finally, register it in the Startup ConfigureServices function:</p>
<pre><code>services.AddTransient&lt;IHttpProxyClientService, HttpProxyClientService&gt;();
</code></pre>
<p>We now have an injectable Service! Now let's sort out the functions.</p>
<p><strong>HttpClient</strong></p>
<p>The CreateHttpClient function is quite simple, if a proxy is required, configure it, otherwise, return a normal HttpClient object. We will pull the details from the configuration and if a proxy is required we will configure the returned HttpClient object to use a HttpClientHandler. The code is as follows:</p>
<pre><code>public HttpClient CreateHttpClient()
    {
    	try
        	{
            	var useProxy = _config.Value.UseProxy;
            	if (!useProxy)
            	{
                		return new HttpClient();
            	}

            	var proxyHost = _config.Value.ProxyHost;
            	var proxyPort = _config.Value.ProxyPort.ToString();

            	var proxy = new WebProxy()
            	{
                		Address = new Uri(&quot;http://&quot; + proxyHost + &quot;:&quot; + proxyPort),
                		UseDefaultCredentials = true
            	};

            	var httpClientHandler = new HttpClientHandler()
            	{
                		Proxy = proxy,
            	};

            	var client = new HttpClient(handler: httpClientHandler, disposeHandler: true);
            	return client;
        	}
        	catch (Exception ex)
        	{
            	Console.WriteLine(&quot;Error: &quot; + ex.Message);
        	}

        	return null;
    }
</code></pre>
<p>Now instead of creating a HttpClient object when we need one, we just use the service instead.</p>
<pre><code>var httpClient = _httpProxyClientService.reateHttpClient()
</code></pre>
<p><strong>HttpWebRequest</strong></p>
<p>It's probably no surprise that the HttpWebRequest will work in exactly the same way except the set up is a little different. Without going into to much detail, this is the code:</p>
<pre><code>public HttpWebRequest CreateHttpWebRequest(string requestUri)
    {
    	try
        	{
            	var useProxy = _config.Value.UseProxy;
            	HttpWebRequest res = (HttpWebRequest)WebRequest.Create(requestUri);

            	if (!useProxy)
            	{
                		return res;
            	}

            	var proxyHost = _config.Value.ProxyHost;
            	var proxyPort = _config.Value.ProxyPort;
            	var myproxy = new WebProxy(proxyHost, proxyPort) { BypassProxyOnLocal = false };
            	res.Proxy = myproxy;
            
            	return res;
        	}
       	catch (Exception ex)
        	{
            	Console.WriteLine(&quot;Error: &quot; + ex.Message);
        	}

        	return null;
    }
</code></pre>
<p><strong>Conclusion</strong></p>
<p>With that, you should now be able to handle Http requests through a proxy following SOLID principles. There's no direct code example for this one I'm afraid although I do use it in my <a href="https://github.com/SRAlexander/Speech-to-text">Speech to Text API Comparator</a> project if you'd like to see it in action. Cheers for reading.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Speech to Text Service Comparisons]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>The last week or so I've been exploring the pros and cons of three Speech to Text services. Bing Speech, IBM Watson and AWS Transcribe using a .Net Core 2.0 backend and an Angular 6 client. These are my findings.</p>
<p><strong>Quick Links</strong></p>
<ul>
<li><a href="https://github.com/SRAlexander/Speech-to-text">Example code for this post</a></li>
<li><a href="https://azure.microsoft.com/en-gb/services/cognitive-services/speech/">Bing Speech</a></li></ul>]]></description><link>http://tech.scottalex.com/speech-to-text-service-comparisons/</link><guid isPermaLink="false">5c83b5653b595e0001a9d993</guid><category><![CDATA[C#]]></category><category><![CDATA[.Net Core 2]]></category><category><![CDATA[Azure]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Watson]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Mon, 21 May 2018 13:23:32 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/05/adult-beautiful-business-864994.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/05/adult-beautiful-business-864994.jpg" alt="Speech to Text Service Comparisons"><p>The last week or so I've been exploring the pros and cons of three Speech to Text services. Bing Speech, IBM Watson and AWS Transcribe using a .Net Core 2.0 backend and an Angular 6 client. These are my findings.</p>
<p><strong>Quick Links</strong></p>
<ul>
<li><a href="https://github.com/SRAlexander/Speech-to-text">Example code for this post</a></li>
<li><a href="https://azure.microsoft.com/en-gb/services/cognitive-services/speech/">Bing Speech</a></li>
<li><a href="https://docs.microsoft.com/en-gb/azure/cognitive-services/speech/home">Bing Speech Documentation</a></li>
<li><a href="https://www.ibm.com/watson/services/speech-to-text/">IBM Watson Speech to Text</a></li>
<li><a href="https://www.ibm.com/watson/developercloud/speech-to-text/api/v1/?cm_mc_uid=15578720400715268925927&amp;cm_mc_sid_50200000=67335031526981995804&amp;cm_mc_sid_52640000=21897971526981939694">IBM Watson Speech to Text Documentation</a></li>
<li><a href="https://aws.amazon.com/">AWS</a></li>
<li><a href="https://aws.amazon.com/s3/">AWS S3</a></li>
<li><a href="https://aws.amazon.com/transcribe/">AWS Transcribe</a></li>
<li><a href="https://docs.aws.amazon.com/transcribe/latest/dg/what-is-transcribe.html">AWS Transcribe Documentation</a></li>
<li><a href="https://aws.amazon.com/visualstudio/">AWS .Net Toolkit</a></li>
<li><a href="https://aws.amazon.com/iam/">AWS IAM</a></li>
</ul>
<p><strong>Bing Speech Service</strong></p>
<p>The first of three that I implemented was the Bing Speech to Text service since I already have an Azure account. The <a href="https://docs.microsoft.com/en-gb/azure/cognitive-services/speech/getstarted/getstarted">documentation</a> is pretty clear and the implementation did not take long to set up with Authentication cookies so Microsoft has done a good job there. Here are the following PROS and CONS:</p>
<p><em><strong>Pros</strong></em></p>
<ul>
<li>Supports the largest range of languages of the three.</li>
<li>It's fast.</li>
<li>Easy to implement.</li>
<li>Three modes: interactive, conversation and dictation.</li>
</ul>
<p><em><strong>Cons</strong></em></p>
<ul>
<li>Some modes are limited to 10 seconds of audio within a 15-second clip.</li>
<li>Modes that can handle more than 10 seconds of audio require a client library to be imported.</li>
</ul>
<p><em><strong>Implementation</strong></em></p>
<p>I've said it's easy to implement, so let me prove it. Firstly grab yourself a subscription key to the free <a href="https://azure.microsoft.com/en-gb/try/cognitive-services/?api=speech-api">Bing Speech API</a> trial <em>Make note of the subscription key</em>. If you're looking at the provided code, put the subscription key in the appsettings.json file in the BingSubscriptionKey field.</p>
<pre><code>&quot;MyConfig&quot;: {
    &quot;UseProxy&quot;: &quot;false&quot;,
    &quot;ProxyHost&quot;: &quot;...&quot;,
    &quot;ProxyPort&quot;: &quot;...&quot;,
    **&quot;BingSubscriptionKey&quot;: &quot;65d5......&quot;** &lt;- Here
}
</code></pre>
<p>Let's have a look at the three steps to the implementation:</p>
<p>Step 1. Get an authentication token from the Azure Authentication Service using your subscription key.</p>
<pre><code># AzureAutenticationService

using (var client = _proxyClientService.CreateHttpClient())
{
    client.DefaultRequestHeaders.Add(&quot;Ocp-Apim-Subscription-Key&quot;, subscriptionKey);
    var uriBuilder = new UriBuilder(fetchUri);
    uriBuilder.Path += &quot;/issueToken&quot;;
    var result = await client.PostAsync(uriBuilder.Uri.AbsoluteUri, null);
    return await result.Content.ReadAsStringAsync();
}
</code></pre>
<p>Step 2. Parse your file/base64 string into a memory stream and buffer it into a HttpWebRequest object.</p>
<pre><code># BingSpeechService

var bytes = Convert.FromBase64String(audioBase64);
using (var memoryStream = new MemoryStream(bytes))
{
    // Open a request stream and write 1024 byte chunks in the stream one at a time.
    using (var requestStream = request.GetRequestStream())
    {
        // Read 1024 raw bytes from the input audio file.
        var buffer = new byte[checked((uint) Math.Min(1024, (int) memoryStream.Length))];
        int bytesRead;
        while ((bytesRead = memoryStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            requestStream.Write(buffer, 0, bytesRead);
        }

        // Flush
        requestStream.Flush();
    }
</code></pre>
<p>Step 3. Call the response and process the JSON response.</p>
<pre><code># BingSpeechService

using (var response = request.GetResponse())
{
    var statusCode = ((HttpWebResponse) response).StatusCode.ToString();
    int.TryParse(statusCode, out var statusCodeInt);

    string responseString;
    using (var sr = new StreamReader(response.GetResponseStream() ?? throw new InvalidOperationException()))
    {
        responseString = sr.ReadToEnd();
    }                
    
    return new SpeechRecognitionResult()
    {
        StatusCode = statusCodeInt,
        JSONResult = responseString,
        ExternalServiceTimeInMilliseconds = sw.ElapsedMilliseconds
    };
}   
</code></pre>
<p>This process can be seen within two services in the provided code, BingSpeechService and AzureAuthenticationService. The settings are passed in through the URL, the main one being language.</p>
<p><em><strong>Results</strong></em></p>
<p>I've tested the process using a few sound files and it seems to be pretty accurate once configured correctly. It roughly takes a couple of seconds to process, so it's fairly snappy too! The response is returned in the form below:</p>
<pre><code>{
    OK
    {
        &quot;RecognitionStatus&quot;: &quot;Success&quot;,
        &quot;Offset&quot;: 22500000,
        &quot;Duration&quot;: 21000000,
        &quot;NBest&quot;: [{
        &quot;Confidence&quot;: 0.941552162,
        &quot;Lexical&quot;: &quot;find a funny movie to watch&quot;,
        &quot;ITN&quot;: &quot;find a funny movie to watch&quot;,
        &quot;MaskedITN&quot;: &quot;find a funny movie to watch&quot;,
        &quot;Display&quot;: &quot;Find a funny movie to watch.&quot;
     }
}
</code></pre>
<p>Overall I've found the service simple to use and effective! Unfortunately, you can only process sound clips up to 10 seconds long with the API alone, any longer and you'll have to use the client library.  I'll compare the features of the service with the other services later in this post.</p>
<hr>
<p><strong>IBM Watson</strong></p>
<p>The second service, IBMs Watson is also a pretty easy service to configure and use, it can be done in one call!</p>
<p><em><strong>Pros</strong></em></p>
<ul>
<li>It's fast.</li>
<li>Easy to implement.</li>
</ul>
<p><em><strong>Cons</strong></em></p>
<ul>
<li>Does not support as many languages as Bing Speech.</li>
</ul>
<p><em><strong>Implementation</strong></em></p>
<p>Implementing Watson is again pretty straightforward, you can sign up for a <a href="https://www.ibm.com/watson/services/speech-to-text/">free account here</a>. The process is exactly the same as Bing Speech except without the Authentication token, the username and password are passed straight in. If you're using the provided code, you will need to add your Watson subscription details into the appsettings.json file.</p>
<pre><code>public SpeechRecognitionResult ParseSpeectToText(string[] args)
{
    var base64String = args[0];
    using (var client = _httpProxyClientService.CreateHttpClient())
    {
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/json&quot;));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            &quot;Basic&quot;, Convert.ToBase64String(
            Encoding.ASCII.GetBytes(_username + &quot;:&quot; + _password)));

        var bytes = Convert.FromBase64String(base64String);
        var content = new StreamContent(new MemoryStream(bytes));
        content.Headers.ContentType = new MediaTypeHeaderValue(&quot;audio/wav&quot;);

        Stopwatch sw = new Stopwatch();
        sw.Start();
            
        var response = client.PostAsync(&quot;https://stream.watsonplatform.net/speech-to-text/api/v1/recognize?continuous=true&amp;_model=&quot; + _model,
                    content).Result;

        if (!response.IsSuccessStatusCode) return null;
        var res = response.Content.ReadAsStringAsync().Result;
            
        sw.Stop();
            
        return new SpeechRecognitionResult()
        {
            JSONResult = res,
            StatusCode = 200,
            ExternalServiceTimeInMilliseconds = sw.ElapsedMilliseconds
        };
    }
}
</code></pre>
<p>Again the URL takes parameters for the settings, so these can be configured as you see fit to serve your purpose. Model is the main parameter that handles language and the quality of the sound file.</p>
<p><em><strong>Results</strong></em></p>
<p>There can be multiple responses from the API but will take on this form:</p>
<pre><code>{
    &quot;results&quot;:[
        {&quot;alternatives&quot;:[{&quot;confidence&quot;:0.764,
         &quot;transcript&quot;:&quot;testing one two three&quot;}],
         &quot;final&quot;:true
     }],
     &quot;result_index&quot;:0,
     &quot;warnings&quot;:[&quot;Unknown arguments:continuous.&quot;
    ]
}
</code></pre>
<p>Overall IBMs Watson is a pretty solid choice. It does not have Bings sound clip time limit which is a plus but does not have as many supported languages which could sway your decision on which one to use. In general testing, it does fall slightly short on accuracy compared to Bing Speech. Next up, AWS Transcribe.</p>
<hr>
<p><strong>AWS Transcribe</strong></p>
<p>AWS transcribe put simply is not the easiest of services to use, it feels like it has been designed to work with Landa expressions meaning you get tied into the AWS infrastructure. However, it does have one major fundamental difference from the other services, it processes punctuation! It does so at a significant cost to its speed though!</p>
<p><em><strong>Pros</strong></em></p>
<ul>
<li>Calculates punctuation.</li>
<li>Sound files can be preserved in S3 storage.</li>
<li>Transcribed files are stored for x amount of time so they don't need to be reprocessed.</li>
</ul>
<p><em><strong>Cons</strong></em></p>
<ul>
<li>Slow.</li>
<li>Tricky to set up.</li>
<li>Very limited language choice.</li>
</ul>
<p><em><strong>Implementation</strong></em></p>
<p>So why is AWS Transcribe a bit tricky to set up? You need to set up S3 storage to store your files, as well as user configuration and the transcribe service itself. If you're handling files already that's fine but if you're using base64 strings from a front end client there's a lot of conversions going on. Firstly sign up for a <a href="https://portal.aws.amazon.com/billing/signup">free account here</a>. You're also going to need the <a href="https://aws.amazon.com/visualstudio/">AWS Visual Studio Toolkit</a>. Before opening Visual Studio, you should run through the <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#create-iam-users">IAM best practices</a> to set up a non-root user. The next time you open Visual Studio you'll be prompted for your username and keys, if you've not got them already you can grab them through the IAM user panel shown below.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/05/Capture.PNG" alt="Speech to Text Service Comparisons"></p>
<p>By providing our details to Visual studio, not only will we now have access to the AWS Explorer. There is also an AWS configuration file configured into our Visual Studio environment. Now that's setup, here are the steps to run the speech to text conversion.</p>
<p>Step 1. Upload a file / base64 string into an S3 bucket. Not just any bucket, one that's been created in us-east-1. You can do this through the AWS explorer panel or through the AWS browser console. Once it exists, the following is the code that will convert your base64 string into a WAV file and upload it to the bucket.</p>
<pre><code>#AmazonUploaderService

byte[] bytes = Convert.FromBase64String(base64);
var filename = &quot;S2C&quot; + DateTime.UtcNow.ToString(&quot;ddMMyyyyhhmmss&quot;) + &quot;.wav&quot;;
var request = new PutObjectRequest
{
    BucketName = _bucketName,
    CannedACL = S3CannedACL.PublicRead,
    Key = filename
};

using (var ms = new MemoryStream(bytes))
{
    request.InputStream = ms;
    await _amazonS3Client.PutObjectAsync(request);
    return new S3UploadResponse()
    {
        BucketName = _bucketName,
        FileRoute = filename,
        BucketRegion = _amazonS3Client.Config.RegionEndpoint.SystemName
    };
}
</code></pre>
<p>Step 2. Create and trigger a transcribe job. Note this is only the triggering, this process won't return the response.</p>
<pre><code>#AWSService

var transciptionJobName = &quot;Transcribe_&quot; + uploadDetails.FileRoute;
var request = new StartTranscriptionJobRequest()
{
    Media = new Media()
    {
        MediaFileUri = &quot;https://s3.&quot; + uploadDetails.BucketRegion + &quot;.amazonaws.com/&quot; + uploadDetails.BucketName + &quot;/&quot; + uploadDetails.FileRoute
    },
    LanguageCode = new LanguageCode(LanguageCode.EnUS),
    MediaFormat = new MediaFormat(&quot;Wav&quot;),
    TranscriptionJobName = transciptionJobName
};
</code></pre>
<p>Step 3. Poll the AWS service for the job status until complete.</p>
<pre><code>#AWSService

while (!jobComplete)
{
    jobRes = await _amazonTranscribeService.GetTranscriptionJobAsync(new GetTranscriptionJobRequest()
    {
        TranscriptionJobName = transciptionJobName
    });

    if (jobRes != null &amp;&amp; jobRes.TranscriptionJob.TranscriptionJobStatus !=
        TranscriptionJobStatus.COMPLETED)
    {
        System.Threading.Thread.Sleep(5000);
    }
        else
    {
        jobComplete = true;
    }
}
</code></pre>
<p>Step 4. Create a HttpClientRequest to the new transcribe job resource (the URL containing the result is returned in the job status request, it's not contained directly).</p>
<pre><code>#AWSService 

using (var client = _httpProxyClientService.CreateHttpClient())
{
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/json&quot;));
    var response = client.GetAsync(jobRes.TranscriptionJob.Transcript.TranscriptFileUri).Result;

    if (!response.IsSuccessStatusCode) return null;
    jsonRes = response.Content.ReadAsStringAsync().Result;
}
</code></pre>
<p>Step 5. Delete the uploaded file from the S3 bucket (otherwise you will be billed for storage at some point).</p>
<pre><code># AmazonUploaderService

var res = await _amazonS3Client.DeleteObjectAsync(_bucketName, filename);
</code></pre>
<p><em><strong>Results</strong></em></p>
<p>The polling process alone puts me off this process, let alone how slow it is. Yet it does not mean that the Transcribe service does not have a place. For non-real-time applications where punctuation is a critical factor then it's ideal! The response is also broken down word by word by default with timestamps which is a nice feature:</p>
<pre><code>&quot;jobName&quot;: &quot;Transcribe_S2C21052018021806.wav&quot;,
&quot;accountId&quot;: &quot;007698288441&quot;,
&quot;results&quot;: {
    &quot;transcripts&quot;: [{
        &quot;transcript&quot;: &quot;Because in one two, three.&quot;
    }],
    &quot;items&quot;: [{
        &quot;start_time&quot;: &quot;0.250&quot;,
        &quot;end_time&quot;: &quot;0.470&quot;,
        &quot;alternatives&quot;: [{
            &quot;confidence&quot;: &quot;0.9666&quot;,
            &quot;content&quot;: &quot;Because&quot;
        }],
        &quot;type&quot;: &quot;pronunciation&quot;
    },
    {
        &quot;start_time&quot;: &quot;0.470&quot;,
        &quot;end_time&quot;: &quot;0.570&quot;,
        &quot;alternatives&quot;: [{
            &quot;confidence&quot;: &quot;0.9993&quot;,
            &quot;content&quot;: &quot;in&quot;
        }],
        &quot;type&quot;: &quot;pronunciation&quot;
    },...
    ]
},
&quot;status&quot;: &quot;COMPLETED&quot;
</code></pre>
<p>In conclusion to AWS, I'd not consider it for any real-time applications but may find some use in offline tasks, especially since it shows itself to be the most accurate of the three when dealing with large files. It should also tie in nicely with other AWS services which is worth bearing in mind.</p>
<p><strong>Key Comparison</strong></p>
<p>The following table has been created from the data contained within the documentation of the three services:</p>
<table style="margin-top:-260px;">
    <thead>
        <th></th>
        <th>Bing Speech</th>
        <th>IMB Watson</th>
        <th>AWS Translate</th>
    </thead>
    <tbody>
        <tr>
            <td>Languages</td>
            <td>Interactive / Dictate mode 29 <br><br>   
<p>•    Arabic (Egypt) (ar-EG)<br>
•    Catalan (ca-ES)<br>
•    Danish (da-DK)<br>
•    German (de-DE)<br>
•    ENG Australian (en-AU)<br>
•    ENG Canadian (en-CA)<br>
•    ENG UK (en-GB)<br>
•    ENG India (en-IN)<br>
•    ENG New Zealand (en-ZN)<br>
•    ENG USA (en-us)<br>
•    Spanish (es-ES)<br>
•    Spanish Mexican (es-MX)<br>
•    Finnish (fi-FI)<br>
•    French Canadian (fr-CA)<br>
•    French France (fe-FR)<br>
•    Hindi (hi-IN)<br>
•    Italian (it-IT)<br>
•    Japanese (ja-JP)<br>
•    Korean (ko-KR)<br>
•    Norwegian (nb-NO)<br>
•    Dutch (nl-NL)<br>
•    Polish (pl-PL)<br>
•    Portuguese Brazil (pt-BR)<br>
•    Russian (ru-RU)<br>
•    Swedish (sv-SE)<br>
•    Chinese Mandarin (zh-CN)<br>
•    Chinese (Hong Kong SAR) (zh-HK)<br>
•    Chinese (Mandarin, Taiwanese) (zh-TW)</p>
<p>Conversation Mode (10)</p>
<p>•    Arabic (ar-EG)<br>
•    German (de-DE)<br>
•    English USA (en-US)<br>
•    Spanish Spain (es-ES)<br>
•    French France (fr-FR)<br>
•    Italian Italy (it-IT)<br>
•    Japanese (ja-JP)<br>
•    Portuguese Brazil (pt-BR)<br>
•    Rusian (ru –RU)<br>
•    Chinease Mandarin (zh.CN)</p></td><br>
<td>Language Model customization<p></p>
<p>•    French<br>
•    Japanese<br>
•    Korean<br>
•    Spanish<br>
•    ENG UK<br>
•    ENG USA</p>
<p>Acoustic Model Customization (Beta)</p>
<p>•    Brazilian Portuguese<br>
•    French<br>
•    Japanese<br>
•    Korean<br>
•    Mandarin Chinese<br>
•    Modern Standard Arabic<br>
•    Spanish<br>
•    UK English<br>
•    US English</p></td><br>
<td><br>
•    English<br>
•    Spanish<p></p>
<p>*Processes punctuation!<br>
</p></td><br>
</tr><br>
<tr><br>
<td>Input Types</td><br>
<td>•    ssml-16khz-16bit-mono-tts<br>
•    raw-16khz-16bit-mono-pcm<br>
•    audio-16khz-16kbps-mono-siren<br>
•    riff-16khz-16kbps-mono-siren<br>
•    riff-16khz-16bit-mono-pcm<br>
•    audio-16khz-128kbitrate-mono-mp3<br>
•    audio-16khz-64kbitrate-mono-mp3<br>
•    audio-16khz-32kbitrate-mono-mp3</td><br>
<td>•    audio/basic<br>
•    audio/flac<br>
•    audio/l16<br>
•    audio/mp3<br>
•    audio/mpeg<br>
•    audio/mulaw<br>
•    audio/ogg<br>
•    audio/ogg;codecs=opus<br>
•    audio/ogg;codecs=vorbis<br>
•    audio/wav<br>
•    audio/webm<br>
•    audio/webm;codecs=opus<br>
•    audio/webm;codecs=vorbis<p></p>
</td>
            <td>16 & 8 kHz audio streams
<p>•    WAV<br>
•    MP3<br>
•    MP4<br>
•    FLAC</p>
</td>
        </tr>
        <tr>
            <td>Websocket support</td>
            <td>Yes</td>
            <td>Yes</td>
            <td>No</td>
        </tr>
        <tr>
            <td>Implementation</td>
            <td>Easy</td>
            <td>Easy</td>
            <td>Complex</td>
        </tr>
        <tr>
            <td>Processing Time</td>
            <td>Seconds</td>
            <td>Seconds</td>
            <td>1-3 Minutes</td>
        </tr>
        <tr>
            <td>Pricing</td>
            <td>•    5,000 free transactions per month
<p>•    £2.982 per 1,000 transactions</p></td><br>
<td>•    100 minutes free per month then…<br>
•    1-250,000 $0.02 per min.<br>
•    250,000 – 500,000 $0.015 per min.<br>
•    500,000-1,000,000 $ 0.0.125 per min.<br>
•    1,000,000 $0.01 per min<p></p>
</td>
            <td>•    60 Minutes free per month for 12 months
<p>•    $0.0004 per second<br>
(minimum per request charge of 15 seconds)</p>
</td>
        </tr>
    </tbody>
</table>
<p><strong>Comparison results</strong></p>
<p>The results can vary greatly, however, when using short phrases Bing Speech comes out on top for accuracy and speed. On the other hand, AWS Transcribes accuracy seems to be the best for long sound files, although slow. An example result can be seen in the image below using the following extract from Frankenstein &quot;It was on a dreary night of November that I beheld the accomplishment of my toils. With an anxiety that almost amounted to agony&quot;.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/05/SpeechComparisonResults.PNG" alt="Speech to Text Service Comparisons"></p>
<p><strong>Conclusion</strong></p>
<p>This has been a nice stepping stone into Speech to Text services, although not perfect they can be considered usable. I feel this project is not complete yet as Azure and AWS have updated services in beta, so the exploration process is likely to continue in the near future. I hope this has been useful so far.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Azure Face API]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>As promised, I've converted the quick training code I created in the CodeIT 2018 Hackathon into a console application. The code can be found <a href="https://github.com/SRAlexander/MicrosftFaceApiTest-Console">here</a>.</p>
<p>There are three key parts to the training process:</p>
<pre><code>* Creating a people group
* Adding people and faces to the group
* Training
</code></pre>
<p>Then of course, after</p>]]></description><link>http://tech.scottalex.com/azure-face-api/</link><guid isPermaLink="false">5c83b5653b595e0001a9d992</guid><category><![CDATA[Azure]]></category><category><![CDATA[Face API]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Fri, 11 May 2018 10:47:28 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/05/Screen-Shot-2018-05-11-at-11.37.02.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/05/Screen-Shot-2018-05-11-at-11.37.02.png" alt="Azure Face API"><p>As promised, I've converted the quick training code I created in the CodeIT 2018 Hackathon into a console application. The code can be found <a href="https://github.com/SRAlexander/MicrosftFaceApiTest-Console">here</a>.</p>
<p>There are three key parts to the training process:</p>
<pre><code>* Creating a people group
* Adding people and faces to the group
* Training
</code></pre>
<p>Then of course, after the training is complete, there is the identification process. Let's have a look at the application and each process.</p>
<p><strong>Application configuration</strong></p>
<p>After grabbing the code, the first thing to do is to configure the code to your machine and Azure settings. Update the subscription key and the location of the root folder in the code below.</p>
<pre><code>    const string SubscriptionKey = &quot;insert subscription key&quot;;
    const string UriBase = &quot;https://northeurope.api.cognitive.microsoft.com/face/v1.0&quot;;
    private static readonly string RootFolder = &quot;Update This Path/MicrosftFaceApiTest-Console&quot;;
    private static readonly string TrainingFolderPath = RootFolder + &quot;/Data/PersonGroupTraining&quot;;
</code></pre>
<p>You will also need the Microsoft.ProjectOxford.Face Nuget package. This will allow us to use the FaceServiceClient which makes using the API service pretty simple:</p>
<pre><code>var faceServiceClient = new  FaceServiceClient(SubscriptionKey, UriBase);
</code></pre>
<p><strong>Creating a People Group</strong></p>
<p>A person group is as exactly what it says it is, a collection on people. The first step is to create one using console option 2. All we need to do is to assign an ID to the group and create it. I've also provided console option 1, to set the &quot;application reference&quot; to an existing people group.</p>
<pre><code>var personGroupId = &quot;&quot;;

static async Task&lt;string&gt; CreatePersonGroup(FaceServiceClient faceServiceClient, string id, string description)
{
    try
    {
        await faceServiceClient.CreatePersonGroupAsync(id, description);
    }
    catch (Exception ex)
    {
        return &quot;Error: &quot; + ex.Message;
    }

    return id;
}
</code></pre>
<p><strong>Adding People and Faces</strong></p>
<p>This is where your project directories become important, the Data folder has been designed to hold other folders named after each person. Inside each person folder should be a number of .jpg images of the individual and only the individual. No images with multiple faces. Have a look at the example Data folder.</p>
<p>We then need to scan the Data directory to find each person to add:</p>
<pre><code>var peopleFolders = GetDirectories(TrainingFolderPath);
        
// Lets add the people
foreach(var potentialPersonDir in peopleFolders)
{
    ....
}
</code></pre>
<p>There are two functions being called within the process above, CreatePerson which adds a person with the name specified (the folder name) and AddFacesToPerson. We can add faces to the person from the GUID returned by the CreatePerson function. It is also possible to add additional data to a person through the userData option such as a user ID from a local database. The two functions themselves really only call the relevant FaceClientService options:</p>
<pre><code>static async Task&lt;CreatePersonResult&gt; CreatePerson(FaceServiceClient faceServiceClient, string groupId, string name,
        string userData)
    {
        try
        {
            CreatePersonResult person = await faceServiceClient.CreatePersonAsync(groupId, name, userData);
            return person;
        }
        catch (Exception ex)
        {
            Console.WriteLine(&quot;Failed to create person &quot; + name);
            Console.WriteLine(&quot;Error: &quot; + ex.Message);
        }

        return null;
    }
    
    static async Task&lt;bool&gt; AddFacesToPerson(FaceServiceClient faceServiceClient, string imagesDirectory,
        string personGroupId, Guid personId, bool limit = false)
    {
        var files = GetFiles(imagesDirectory, &quot;*.JPG&quot;).Union(GetFiles(imagesDirectory, &quot;*.jpg&quot;)).ToArray();;
        foreach (var imagePath in files)
        {
            using (Stream s = File.OpenRead(imagePath))
            {
                // Detect faces in the image and add to person
                try
                {
                    await faceServiceClient.AddPersonFaceAsync(personGroupId, personId, s);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(&quot;An error has occured with image &quot; + imagePath + &quot; and has not been added to person&quot;);
                    Console.WriteLine(&quot;Error: &quot; + ex.Message);
                }
            }

            if (limit)
            {
                System.Threading.Thread.Sleep(15000);
            }
        }

        return true;
    }
</code></pre>
<p>With that you know have your facial images ready for training.</p>
<p><strong>Training</strong></p>
<p>Training is the easy part, just make a call to train and wait for it to complete:</p>
<pre><code>static async void TrainPersonGroup(FaceServiceClient faceServiceClient, string personGroupId)
    {
        await faceServiceClient.TrainPersonGroupAsync(personGroupId);
    }
    
    static async Task&lt;bool&gt; WaitForPersonGroupTraining(FaceServiceClient faceServiceClient, string personGroupId,
        bool limit = false)
    {
        while(true)
        {
            var trainingStatus = await faceServiceClient.GetPersonGroupTrainingStatusAsync(personGroupId);

            if (trainingStatus.Status != Status.Running)
            {
                return true;
            }

            if (limit)
            {
                System.Threading.Thread.Sleep(15000);
            }
        }    
    }
</code></pre>
<p><strong>Identification</strong></p>
<p>The final part is to test your training through the identification process. Firstly we pass in an image and look to see if there are any faces present:</p>
<pre><code>var faces = await faceServiceClient.DetectAsync(s);
var faceIds = faces.Select(face =&gt; face.FaceId).ToArray();
</code></pre>
<p>We then pass the detected faces into the identify call:</p>
<pre><code>var results = await faceServiceClient.IdentifyAsync(personGroupId, faceIds);
</code></pre>
<p>We will get a list of GUIDs for all identified individuals. If we require more information such as the name of the individual and the userData we passed into the creation process, we just need to Call GetPerson with the GUID:</p>
<pre><code>var person = await faceServiceClient.GetPersonAsync(personGroupId, candidateId);
</code></pre>
<p>The identification process is the only part that you will need to build into any application using your newly trained API.</p>
<p><strong>Conclusion</strong></p>
<p>The Azure Face API is a powerful piece of technology that is surprisingly easy to implement. At its cost point its hard to imagine why you'd go about developing your own implementation. Have fun playing with it!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[The Quest for Identity : Part 2 - Angular Client]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>The quest continues, this time to get an Angular client authenticated against our Identity server.</p>
<p>First of all, let's get a basic Angular app going:</p>
<pre><code>$ ng new AngClient --routing -prefix ac --skip-install
$ cd AngClient
$ yarn or npm install
</code></pre>
<p>With that, we can now follow another great post by Scott Brady</p>]]></description><link>http://tech.scottalex.com/the-quest-for-identity-part-2/</link><guid isPermaLink="false">5c83b5653b595e0001a9d990</guid><category><![CDATA[Identity Server 4]]></category><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Thu, 10 May 2018 09:35:45 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/04/Angular-1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/04/Angular-1.png" alt="The Quest for Identity : Part 2 - Angular Client"><p>The quest continues, this time to get an Angular client authenticated against our Identity server.</p>
<p>First of all, let's get a basic Angular app going:</p>
<pre><code>$ ng new AngClient --routing -prefix ac --skip-install
$ cd AngClient
$ yarn or npm install
</code></pre>
<p>With that, we can now follow another great post by Scott Brady - <a href="https://www.scottbrady91.com/Angular/SPA-Authentiction-using-OpenID-Connect-Angular-CLI-and-oidc-client">SPA Authentication using OpenID Connect Angular CLI and oidc client</a>, slightly dated again, however, we can happily follow this guide up untill &quot;<strong>Calling a Protected API</strong>&quot;. I would recommend some changes though to maintain a good code structure, here are my amended instructions.</p>
<p><strong>Protecting a Component &amp; Route Guard</strong></p>
<p>The main problem with the structure that I have here is that everything gets merged together, plus you may have pages that don't need authenticating so let's do the following in the CLI:</p>
<pre><code>$ ng g c components/protects
$ ng g c components/home
</code></pre>
<p>Then in the app-routing.module.ts, update the routes array to look like the following:</p>
<pre><code>import { ProtectedComponent } from './components/protected/protected.component';
import { HomeComponent } from './components/home/home.component';

const routes: Routes = [
  {
    path: '',
    component: HomeComponent
  },
  {
    path: 'protected',
    component: ProtectedComponent
  }
];
</code></pre>
<p>Next, let's configure the app.component.html to display the routes through the following no thrills navigation menu:</p>
<pre><code>&lt;h1&gt;
  {{title}}
&lt;/h1&gt;
&lt;router-outlet&gt;&lt;/router-outlet&gt;
</code></pre>
<p><img src="http://tech.scottalex.com/content/images/2018/04/angsec-1-1.PNG" alt="The Quest for Identity : Part 2 - Angular Client"></p>
<p><strong>Route Guard</strong></p>
<p>The route guard is more of a shared component so let's make it so. Firstly let's create a shared module:</p>
<pre><code> $ ng g m shared
</code></pre>
<p>Then let's move the common modules from app.module.ts into the shared module, export them and then import the shared module into app.module.</p>
<pre><code># shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';

@NgModule({
  imports: [
    CommonModule
  ],
  exports : [
    CommonModule,
    BrowserModule
  ],
  declarations: []
})
export class SharedModule { }

# app.module.ts

...
import { SharedModule } from './shared/shared.module';

@ngModule({
    ...,
    imports: [
    AppRoutingModule,
    SharedModule
      ]
    }
)
</code></pre>
<p>Now let's create our Authguard service and implement it:</p>
<pre><code>$ ng g s shared/services/authguard

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';

@Injectable()
export class AuthGuardService implements CanActivate {
    canActivate(): boolean {
        return false;
    }
}
</code></pre>
<p>Then declare it as a provider in the shared.module.ts:</p>
<pre><code> providers: [AuthGuardService]
</code></pre>
<p>Finally, we want to use this guard within our routes so that any route that requires our authentication, calls the guard's canActivate function prior to any of the routes components lifecycle hooks.</p>
<pre><code>#app-routing.mdoule.ts

{
    path: 'protected',
    component: ProtectedComponent,
    canActivate: [AuthGuardService]
}
</code></pre>
<p>If you run the application now, you should no longer be able to access the protected route!</p>
<p><strong>The oidc-client</strong></p>
<p>Now onto the authentication, first of all, create another service called auth and add it into out sharedModule providers.</p>
<pre><code>$ ng g s shared/services/auth

#shared.module.ts
import { AuthService } from './services/auth.service';

...
providers: [AuthGuardService, AuthService]
</code></pre>
<p>Next up we need the oidc-client package, through the command line type:</p>
<pre><code>yard add oidc-client
or
npm install oidc-client
</code></pre>
<p>We can now use the user manager components from oidc-client in our auth service:</p>
<pre><code>#auth.service.ts
import { UserManager, UserManagerSettings, User } from 'oidc-client';
</code></pre>
<p>Next up we need to sort out out client settings to tie in with our identity server. In the auth service add the following code, filling in the authority field with your identity server endpoint address:</p>
<pre><code>#auth.service.ts
@Injectable()
export class AuthService {
}
export function getClientSettings(): UserManagerSettings {
  return {
    authority: 'https://localhost:443xx/',
    client_id: 'ng',
    redirect_uri: 'http://localhost:4200/auth-callback',
    post_logout_redirect_uri: 'http://localhost:4200/',
    response_type: &quot;id_token token&quot;,
    scope: &quot;openid profile&quot;,
    filterProtocolClaims: true,
    loadUserInfo: true,
    automaticSilentRenew: true,
    silent_redirect_uri: 'http://localhost:4200/silent-refresh.html'
};
</code></pre>
<p>Later down the line you should configure these settings to a config file! Next up, we want to make sure we have a user on the construction of our AuthService with the following:</p>
<pre><code>#auth.service.ts
private manager: UserManager = new UserManager(getClientSettings());
private user: User = null;

constructor() {
    this.manager.getUser().then(user =&gt; {
        this.user = user;
    });
}
</code></pre>
<p>Then finally for our auth service, we need to add five self-explained functions, if you need more info on these functions have a look at Scott Brady's post which will go into more detail.</p>
<pre><code>#auth.service.ts
isLoggedIn(): boolean {
    return this.user != null &amp;&amp; !this.user.expired;
  }

  getClaims(): any {
    return this.user.profile;
  }

  getAuthorizationHeaderValue(): string {
    return `${this.user.token_type} ${this.user.access_token}`;
  }

  startAuthentication(): Promise&lt;void&gt; {
    return this.manager.signinRedirect();
  }

  completeAuthentication(): Promise&lt;void&gt; {
      return this.manager.signinRedirectCallback().then(user =&gt; {
      this.user = user;
    });
  }
</code></pre>
<p>Now we can use the AuthService in our AuthGuardService by implementing the isLoggedIn and startAuthentication function. They are used within the constructor and canActivate function:</p>
<pre><code>#authguard.service.ts
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable()
export class AuthGuardService implements CanActivate {
    constructor(private authService: AuthService) { }

    canActivate(): boolean {
        if(this.authService.isLoggedIn()) {
          return true;
        }

        this.authService.startAuthentication();
        return false;
    }
}
</code></pre>
<p>There is one final task within the angular app, that is to handle the callback endpoint from our identity server and complete the authentication process. We will need a component to do so, so let's create it:</p>
<pre><code>ng g c shared/components/auth-callback -m shared
</code></pre>
<p>Then fill it in with the following:</p>
<pre><code>import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';

@Component({
    selector: 'app-auth-callback',
    templateUrl: './auth-callback.component.html',
    styleUrls: ['./auth-callback.component.css']
})

export class AuthCallbackComponent implements OnInit {

    constructor(private authService: AuthService) { }

    ngOnInit() {
      this.authService.completeAuthentication();
    }
}
</code></pre>
<p>Make sure we're exporting it in our shared.module.ts</p>
<pre><code>#shared.module.ts
import { AuthCallbackComponent } from './components/auth-callback/auth-callback.component';

...

exports : [ ..., 
    AuthCallbackComponent
]
</code></pre>
<p>And finally set up a route so that our Identity Server can reach our Angular app's callback component:</p>
<pre><code>#app-routing.module.ts

const routes: Routes = [
...,
{
    path: 'auth-callback',
    component: AuthCallbackComponent
}]
</code></pre>
<p>And that's it for our Angular app, however, we still need to add it as a client within our Identity Server. Open up our Identity Server project and head to the config.cs file. Then within clients add the following:</p>
<pre><code>#config.cs
new Client
{
    ClientId = &quot;ng&quot;,
    ClientName = &quot;Angular 4 Client&quot;,
    AllowedGrantTypes = GrantTypes.Implicit,
    RedirectUris = new List&lt;string&gt; { &quot;http://localhost:4200/auth-callback&quot; },
    PostLogoutRedirectUris = new List&lt;string&gt; { &quot;http://localhost:4200/&quot; },
    AllowedCorsOrigins = new List&lt;string&gt; { &quot;http://localhost:4200&quot; },
    AllowAccessTokensViaBrowser = true,

    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
    }
}
</code></pre>
<p>Good news we are finally there! Run the Identity Server project and Angular project together and head to your home page, no problem. Now click on the &quot;Protected&quot; link and you should be redirected to the Identity Server login page. Use your credentials and sign in and surprise, you should get redirected to our auth-callback page. Click on the &quot;Protected&quot; link again and you should now see &quot;protected works!&quot;. Awesome we can now block pages from unauthorised users!</p>
<p>That's as far as I'm going to go with this post, however, part 3 will be about angular passing its token onto a standalone API. There are also a couple of things you can do at this point to improve your app listed below. Cheers for reading.</p>
<p>The completed code for this post can be found <a href="https://github.com/SRAlexander/IS4-ASPUsers/tree/AngularAuth">here</a>.</p>
<ul>
<li>You probably want to modify the auth-callback page to record what page redirected you to it. That way once the Identity Server is done you can go straight back to the page they originally tried to access.</li>
<li>Your token will expire eventually and we have no control over that yet. Check it out <a href="https://www.scottbrady91.com/OpenID-Connect/Silent-Refresh-Refreshing-Access-Tokens-when-using-the-Implicit-Flow">here</a>.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[CodeIT 18 Hackathon]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Yesterday I was introduced to the world of Hackathons through Civica Digital's CodeIT18 event in Bristol. Teamed up with <a href="https://github.com/BanksySan">David Banks</a> and <a href="https://www.linkedin.com/in/matt-cantillon/">Matthew Cantilion</a> we had a pretty well rounded full stack team, even if it was our first time working together. Part of the prerequisites of the day was</p>]]></description><link>http://tech.scottalex.com/codeit-hackerthon/</link><guid isPermaLink="false">5c83b5653b595e0001a9d991</guid><category><![CDATA[C#]]></category><category><![CDATA[Angular]]></category><category><![CDATA[Azure]]></category><category><![CDATA[Face API]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Fri, 04 May 2018 15:57:36 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/05/IMG_6097.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/05/IMG_6097.jpg" alt="CodeIT 18 Hackathon"><p>Yesterday I was introduced to the world of Hackathons through Civica Digital's CodeIT18 event in Bristol. Teamed up with <a href="https://github.com/BanksySan">David Banks</a> and <a href="https://www.linkedin.com/in/matt-cantillon/">Matthew Cantilion</a> we had a pretty well rounded full stack team, even if it was our first time working together. Part of the prerequisites of the day was to choose a specific scenario, in which we decided to challenge ourselves with a facial recognition task. This is how the day played out for us.</p>
<p><strong>08:00</strong>: Introduction to the seven teams, set up and breakfast. Free food is always welcome!</p>
<p><strong>08:30</strong>: We're off! The first point of call was to come up with a plan. We quickly mocked up some screen designs and a user workflow using <a href="https://balsamiq.com/">Balsamiq</a>. Then when we were happy, we set up a GitHub repository, Angular 5 frontend and a .NET Core 2 back API. Perfect since Matt and I are Mac users allowing us to work on the backend too.</p>
<p><strong>09:15</strong>: The skeleton of the front and backend parts is up and running. Snappy! Matthew has not used Angular before so he's having to pick it up quite fast but the CLI is proving invaluable for its speed. David's hit the first snag of the day. The police database API is not liking basic authentication. Matt and I will continue with the basic manual search functionality features required by the scenario, just in case we fail on the face detection section of our project.</p>
<p><strong>10:00</strong>: David's fixed the authentication issue, who knew the authentication header is case sensitive! He's also got the searching functionality working with the police API so that's one key piece of functionality complete, as well as a basic form of authentication for our own API. Accountability with this type of system is key! Matt and I, on the other hand, have sorted out a login page and searching form. It's going well so far.</p>
<p><strong>10:45</strong>: We now have JSON server up and running and a few services pulling back mock data for our search results on the frontend. Matt's now continuing to develop the page to display them. Image data is currently missing as it seems to be missing from the police API which provides a challenge. David's been working some magic and is now waiting to tie the front and back together. 11:30 has been agreed as the combining time.</p>
<p><strong>11:30</strong>: Matt has finished the results page and is now ready to tie the frontend together with the back with David. I've also started on implementing the camera functionality within the frontend.</p>
<p><strong>12:00</strong>: Lunch time calls! We are now in a strong position if the facial recognition fails we now have the manual search process functional and tied together with our backend. The real fun starts soon!</p>
<p><strong>12:30</strong>: Facial identification time. We've not been able to get back any photos from the police API yet so I've had to take a couple of photos from a few people around the event. Three images of each person should create a good model during the training process. Matt is now working on tidying up the frontend into a user-friendly state. David is working on some issues with the police API and publishing while I'm working on the facial recognition training process.</p>
<p><strong>14:30</strong>: Facial identification is working! The Azure face API is actually quite impressive even if it did throw a few spanners in the works by not returning error messages during the training process. I also managed to assign IDs of individuals from the police API into the face data. All my willing victims from earlier have now been turned into criminals. Sorry! The frontend is looking good and is ready to now implement the facial identification process. David has been fighting some publishing issues but nothing that should affect the end result.</p>
<p><strong>16:00</strong>: The frontend is now passing an image back successfully, the facial recognition is detecting faces as required and returning both the azure GUID and our police API ID. David has been working on returning the individuals data from the police API and should be ready to tie in shortly to the template page we created earlier.</p>
<p><strong>17:30</strong>: Matt has produced us a nice presentation for the end show. David and I have been battling Git merge issues and multiple communication issues. The final half an hour is going to be a rush to tie this all together.</p>
<p><strong>18:00</strong>: Just made it, the functionality that we set out to create has been produced! A few little tweaks to some JSON strings and ID manipulation (as they start at 108 not 1) and we've officially hacked are the way to a working solution. David has also snuck in Azure insights at some point so every API call is now being logged. Magic! 62 commits and now all that's left is to present our work.</p>
<p><strong>19:15</strong>: Presentations completed! A few minor issues but that's what happens when you only finish your solution five minutes before the end. A couple of strong contenders, it's going to be close.</p>
<p><strong>19:45</strong>: Winners announced, Congratulations CrEYEm Labs! Now off for a well-earned pint! Cheers everyone and a big thank you to the organisers and everyone else who helped in the running of the day!</p>
<p><img src="http://tech.scottalex.com/content/images/2018/05/17a8aa05-5f49-4735-99a2-2f4776bd06f1-original.jpeg" alt="CodeIT 18 Hackathon"></p>
<p>Looking back on the day we didn't kill each other and our solution was fairly successful. Maybe we should have done a bit more research into the provided API's responses earlier on and maybe we could have done with an extra member to help with polishing up the solution but ultimately we're happy with the result of the day. Our code can be found <a href="https://github.com/SRAlexander/Hackathon-2018">here</a> and I'll produce another post on the Azure Face API training process in the near future.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Angular 5 Dojo @ Civica Digital Bath / Hammersmith]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Last night I had the pleasure of co-hosting with <a href="https://www.linkedin.com/in/raquelplaza/">Raquel Plaza</a> and <a href="https://www.linkedin.com/in/digitaldelivery/">Richard Press</a>, the first joint Dojo between the Bath and Hammersmith Civica offices. I hope everyone enjoyed their introduction to the Angular CLI, or if you were a bit more advanced, managed to take away something new with</p>]]></description><link>http://tech.scottalex.com/civica-digital-angular-5-dojo/</link><guid isPermaLink="false">5c83b5653b595e0001a9d98f</guid><category><![CDATA[Angular]]></category><category><![CDATA[Dojo]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Fri, 27 Apr 2018 16:23:42 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2019/06/dojo-pic-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2019/06/dojo-pic-1.jpg" alt="Angular 5 Dojo @ Civica Digital Bath / Hammersmith"><p>Last night I had the pleasure of co-hosting with <a href="https://www.linkedin.com/in/raquelplaza/">Raquel Plaza</a> and <a href="https://www.linkedin.com/in/digitaldelivery/">Richard Press</a>, the first joint Dojo between the Bath and Hammersmith Civica offices. I hope everyone enjoyed their introduction to the Angular CLI, or if you were a bit more advanced, managed to take away something new with you.</p>
<p>For all those who attended and those who would like to see the material, you can find it <a href="https://github.com/CivicaDigital/angular5-dojo">here</a>. I will try and keep this updated to the best of my ability.</p>
<p>I would also recommend the following resources to continue down your Angular path.</p>
<ul>
<li>Pipelines – Chapter 6 Data Binding and Pipes &gt; <a href="https://app.pluralsight.com/library/courses/angular-2-getting-started-update/table-of-contents">Transforming Data with Pipes</a> by Deborah Kurata</li>
<li><a href="https://medium.com/@ryanchenkie_40935/angular-authentication-using-the-http-client-and-http-interceptors-2f9d1540eb8">Interceptors</a> by Ryan Chenkie</li>
<li><a href="https://www.scottbrady91.com/Angular">Angular with Identity Server</a> by Scott Brady</li>
<li><a href="https://app.pluralsight.com/library/courses/best-practices-angular/table-of-contents">Best Practises</a> by Jim Cooper</li>
</ul>
<p>Cheers for attending everyone!</p>
<p>Edit 1 27/04/2018 - A couple of people asked about why some modules use .forRoot() and .forChild() which I could not answer last night. However, <a href="https://www.linkedin.com/in/gareth-penfold-16920243/">Gareth Penfold</a> the eager bee he is, had already gone away to look it up and has recomended the following <a href="http://angularfirst.com/the-ngmodule-forroot-convention/">post</a>. Cheers Gareth :).</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[The Quest for Identity : Part 1]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I wanted to explore the concept of identity, authentication and authorisation in more detail after I acquired some hands-on experience with Identity Server 3 on my last project. This post is about my findings along the way and hopefully the development of a modern authorisation structure.</p>
<p>My first stop was</p>]]></description><link>http://tech.scottalex.com/is4-the-quest-for-identity/</link><guid isPermaLink="false">5c83b5653b595e0001a9d98e</guid><category><![CDATA[Identity Server 4]]></category><category><![CDATA[C#]]></category><category><![CDATA[.Net Core 2]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Thu, 26 Apr 2018 10:43:04 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/04/vampreflect.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/04/vampreflect.jpg" alt="The Quest for Identity : Part 1"><p>I wanted to explore the concept of identity, authentication and authorisation in more detail after I acquired some hands-on experience with Identity Server 3 on my last project. This post is about my findings along the way and hopefully the development of a modern authorisation structure.</p>
<p>My first stop was to explore modern security concepts and workflows in which I came across Dominick Baier's slightly dated Pluralsight Course - <a href="https://app.pluralsight.com/library/courses/oauth2-json-web-tokens-openid-connect-introduction/table-of-contents">Introduction to OAuth2, OpenID Connect and JSON Web Tokens (JWT)</a>.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/04/ps-dombai.png" alt="The Quest for Identity : Part 1"></p>
<p>This course took about 2 hours to complete and personally I found it quite useful to build a fundamental understanding of concepts. However,  I've also found some further resources on each item of interest for anyone who does not have access to PluralSight.</p>
<ul>
<li>oAuth2 - <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2">An Introduction to oAuth2</a></li>
<li>OpenId - <a href="https://nordicapis.com/api-security-oauth-openid-connect-depth/">API Security: Deep Dive into OAuth and OpenID Connect</a></li>
</ul>
<p>My next point of call was to start implementing authentication in which I found a nice course by Chris Klug on PluralSight - <a href="https://app.pluralsight.com/library/courses/aspnet-core-identity-management-playbook/table-of-contents">ASP.NET Core 2 Authentication Playbook</a>.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/04/ps-chrisKlug.png" alt="The Quest for Identity : Part 1"></p>
<p>He's a little fast in places but he does cover a lot of different methods of authentication, each chapter is a different process and can easily be used as a return reference point. I've not implemented every method but some of the key chapters for me were:</p>
<ul>
<li><a href="https://github.com/SRAlexander/PS-ASP.NET-Core-2-Authentication-Playbook/tree/localLogins">Using Local Logins</a></li>
<li>Authenticating Users Using Azure AD B2C</li>
<li>Authenticating Users Using OpenID Connect and IdentityServer</li>
<li>Setting up Authorization</li>
</ul>
<p>It's a really good course in terms of providing a basic overview of how to implement the different methods of authentication however, I want to implement something a little more complex than the basics. I want users stored in a database at the least.</p>
<p>The best way I can think of doing this is to tie identity server 4 in with ASP.Net users. To do this I originally followed Scott Bradys &quot;<a href="https://www.scottbrady91.com/Identity-Server/Getting-Started-with-IdentityServer-4">Getting Started with IdentityServer 4</a>&quot; blog post. This tutorial has some good material particularly in terms of the MVC client (which is worth understanding) however, it is outdated when it comes to the ASP.Net user integration. The best resource I found for this part was actually the <a href="https://identityserver4.readthedocs.io/en/release/quickstarts/6_aspnet_identity.html">Identity Server official documentation</a>. Beware though, a lot of it is a muddle of different versions at the time of posting this blog!</p>
<p>You can successfully follow the documentation up untill &quot;<strong>Logging in with the MVC client to set up ASP.Net users with an identity server-driven login system</strong>&quot; before it gets a bit lost. There are a couple of things to look out for when running through this tutorial:</p>
<ul>
<li>When you reach modify hosting, why not just enable SSL from the start and copy the SSL URL as the project start URL (Make note of the URL for future required references).</li>
<li>When you reach creating the user database you will need to enter the project folder through the package manager to be able to use the dotnet ef command.</li>
</ul>
<p><img src="http://tech.scottalex.com/content/images/2018/04/vs-ssl.PNG" alt="The Quest for Identity : Part 1"></p>
<p>You should now be able to create a user, login and logout through the default ASP theme! Do so before continuing as we will be removing the create functionality. The next part of the tutorial is where it becomes unclear, these are the steps that I took to get it working.</p>
<p>Firstly grab the <a href="https://github.com/IdentityServer/IdentityServer4.Quickstart.UI">Identity Server Quickstart UI</a>. We are going to replace the default ASP theme with the Quickstart UI since the authentication endpoints have already been configured for us. For now move the wwwroot, Views and Quickstart folders into a new folder called old. Then copy the new versions in from the download. There will now be a couple of references that need updating/removing (e.g. Account controller) just run a build to find the errors.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/04/is4home.PNG" alt="The Quest for Identity : Part 1"></p>
<p>Secondly, we need to grab the MVC client referenced in the documentation. What is not clear though is that all the documentation code examples can be found <a href="https://github.com/IdentityServer/IdentityServer4.Samples">here</a>. Grab the MVC client from the relevant sample (Quickstarts &gt; 6_AspNetIdentity) and import it into your project.</p>
<p>The MVC Client needs to be configured to point to our identity server. Head into the startup.cs file and change the authority field to your identity server URL &quot;https://localhost:443xx/&quot;. We also need to update our client, firstly enable SSL and update the default URL in the MVC Client in the same manner as we did previously. Then in the server projects, config file update the following URLs...</p>
<p>RedirectUris = { &quot;https://localhost:443xx/signin-oidc&quot; },<br>
PostLogoutRedirectUris = { &quot;https://localhost:443xx/signout-callback-oidc&quot; },</p>
<p>To run the project, right click on your solution and select &quot;Set Startup Projects&quot;. so we can see everything thats going on I've set mine to start both projects under the multiple option.</p>
<p>The application will now run but we have a problem, by default the quickstart UI uses Username as a required field whereas ASP Users uses Email. We will have to change that, head into quickstart &gt; account &gt; logininputmodel.cs and change username to email. Build the project and you'll find a few missing references to the username which you can change over to email. There will also be some references that won't get caught in the build under views &gt; account &gt; login.cshtml. They will also need changing to email.</p>
<p>Finally, we need to update the account controller to a hybrid controller that I've developed from multiple sources. Just completely change the controller to the following:</p>
<pre><code>namespace IS4Server.Controllers.Account.Account
{
    [SecurityHeaders]
    public class AccountController : Controller
    {
        private readonly UserManager&lt;ApplicationUser&gt; _userManager;
        private readonly SignInManager&lt;ApplicationUser&gt; _signInManager;
        private readonly IIdentityServerInteractionService _interaction;
        private readonly IClientStore _clientStore;
        private readonly IAuthenticationSchemeProvider _schemeProvider;
        private readonly IEventService _events;

        public AccountController(
            UserManager&lt;ApplicationUser&gt; userManager,
            SignInManager&lt;ApplicationUser&gt; signInManager,
            IIdentityServerInteractionService interaction,
            IClientStore clientStore,
            IAuthenticationSchemeProvider schemeProvider,
            IEventService events)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _interaction = interaction;
            _clientStore = clientStore;
            _schemeProvider = schemeProvider;
            _events = events;
        }

        /// &lt;summary&gt;
        /// Show login page
        /// &lt;/summary&gt;
        [HttpGet]
        public async Task&lt;IActionResult&gt; Login(string returnUrl)
        {
            // build a model so we know what to show on the login page
            var vm = await BuildLoginViewModelAsync(returnUrl);

            if (vm.IsExternalLoginOnly)
            {
                // we only have one option for logging in and it's an external provider
                return await ExternalLogin(vm.ExternalLoginScheme, returnUrl);
            }

            return View(vm);
        }

        /// &lt;summary&gt;
        /// Handle postback from username/password login
        /// &lt;/summary&gt;
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task&lt;IActionResult&gt; Login(LoginInputModel model, string button)
        {
            if (button != &quot;login&quot;)
            {
                // the user clicked the &quot;cancel&quot; button
                var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
                if (context != null)
                {
                    // if the user cancels, send a result back into IdentityServer as if they 
                    // denied the consent (even if this client does not require consent).
                    // this will send back an access denied OIDC error response to the client.
                    await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);

                    // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
                    return Redirect(model.ReturnUrl);
                }
                else
                {
                    // since we don't have a valid context, then we just go back to the home page
                    return Redirect(&quot;~/&quot;);
                }
            }

            if (ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberLogin, lockoutOnFailure: true);
                if (result.Succeeded)
                {
                    var user = await _userManager.FindByEmailAsync(model.Email);
                    await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName));

                    // make sure the returnUrl is still valid, and if so redirect back to authorize endpoint or a local page
                    // the IsLocalUrl check is only necessary if you want to support additional local pages, otherwise IsValidReturnUrl is more strict
                    if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl))
                    {
                        return Redirect(model.ReturnUrl);
                    }

                    return Redirect(&quot;~/&quot;);
                }

                await _events.RaiseAsync(new UserLoginFailureEvent(model.Email, &quot;invalid credentials&quot;));

                ModelState.AddModelError(&quot;&quot;, AccountOptions.InvalidCredentialsErrorMessage);
            }

            // something went wrong, show form with error
            var vm = await BuildLoginViewModelAsync(model);
            return View(vm);
        }

        /// &lt;summary&gt;
        /// initiate roundtrip to external authentication provider
        /// &lt;/summary&gt;
        [HttpGet]
        public async Task&lt;IActionResult&gt; ExternalLogin(string provider, string returnUrl)
        {
            if (AccountOptions.WindowsAuthenticationSchemeName == provider)
            {
                // windows authentication needs special handling
                return await ProcessWindowsLoginAsync(returnUrl);
            }
            else
            {
                // start challenge and roundtrip the return URL and 
                var props = new AuthenticationProperties()
                {
                    RedirectUri = Url.Action(&quot;ExternalLoginCallback&quot;),
                    Items =
                    {
                        { &quot;returnUrl&quot;, returnUrl },
                        { &quot;scheme&quot;, provider },
                    }
                };
                return Challenge(props, provider);
            }
        }

        /// &lt;summary&gt;
        /// Post processing of external authentication
        /// &lt;/summary&gt;
        [HttpGet]
        public async Task&lt;IActionResult&gt; ExternalLoginCallback()
        {
            // read external identity from the temporary cookie
            var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
            if (result?.Succeeded != true)
            {
                throw new Exception(&quot;External authentication error&quot;);
            }

            // lookup our user and external provider info
            var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result);
            if (user == null)
            {
                // this might be where you might initiate a custom workflow for user registration
                // in this sample we don't show how that would be done, as our sample implementation
                // simply auto-provisions new external user
                user = await AutoProvisionUserAsync(provider, providerUserId, claims);
            }

            // this allows us to collect any additonal claims or properties
            // for the specific prtotocols used and store them in the local auth cookie.
            // this is typically used to store data needed for signout from those protocols.
            var additionalLocalClaims = new List&lt;Claim&gt;();
            var localSignInProps = new AuthenticationProperties();
            ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
            ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps);
            ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps);

            // issue authentication cookie for user
            // we must issue the cookie maually, and can't use the SignInManager because
            // it doesn't expose an API to issue additional claims from the login workflow
            var principal = await _signInManager.CreateUserPrincipalAsync(user);
            additionalLocalClaims.AddRange(principal.Claims);
            var name = principal.FindFirst(JwtClaimTypes.Name)?.Value ?? user.Id;
            await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, name));
            await HttpContext.SignInAsync(user.Id, name, provider, localSignInProps, additionalLocalClaims.ToArray());

            // delete temporary cookie used during external authentication
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

            // validate return URL and redirect back to authorization endpoint or a local page
            var returnUrl = result.Properties.Items[&quot;returnUrl&quot;];
            if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }

            return Redirect(&quot;~/&quot;);
        }

        /// &lt;summary&gt;
        /// Show logout page
        /// &lt;/summary&gt;
        [HttpGet]
        public async Task&lt;IActionResult&gt; Logout(string logoutId)
        {
            // build a model so the logout page knows what to display
            var vm = await BuildLogoutViewModelAsync(logoutId);

            if (vm.ShowLogoutPrompt == false)
            {
                // if the request for logout was properly authenticated from IdentityServer, then
                // we don't need to show the prompt and can just log the user out directly.
                return await Logout(vm);
            }

            return View(vm);
        }

        /// &lt;summary&gt;
        /// Handle logout page postback
        /// &lt;/summary&gt;
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task&lt;IActionResult&gt; Logout(LogoutInputModel model)
        {
            // build a model so the logged out page knows what to display
            var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

            if (User?.Identity.IsAuthenticated == true)
            {
                // delete local authentication cookie
                await _signInManager.SignOutAsync();

                // raise the logout event
                await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
            }

            // check if we need to trigger sign-out at an upstream identity provider
            if (vm.TriggerExternalSignout)
            {
                // build a return URL so the upstream provider will redirect back
                // to us after the user has logged out. this allows us to then
                // complete our single sign-out processing.
                string url = Url.Action(&quot;Logout&quot;, new { logoutId = vm.LogoutId });

                // this triggers a redirect to the external provider for sign-out
                return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
            }

            return View(&quot;LoggedOut&quot;, vm);
        }

        /*****************************************/
        /* helper APIs for the AccountController */
        /*****************************************/
        private async Task&lt;LoginViewModel&gt; BuildLoginViewModelAsync(string returnUrl)
        {
            var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
            if (context?.IdP != null)
            {
                // this is meant to short-circuit the UI and only trigger the one external IdP
                return new LoginViewModel
                {
                    EnableLocalLogin = false,
                    ReturnUrl = returnUrl,
                    Email = context?.LoginHint,
                    ExternalProviders = new ExternalProvider[] { new ExternalProvider { AuthenticationScheme = context.IdP } }
                };
            }

            var schemes = await _schemeProvider.GetAllSchemesAsync();

            var providers = schemes
                .Where(x =&gt; x.DisplayName != null ||
                            (x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
                )
                .Select(x =&gt; new ExternalProvider
                {
                    DisplayName = x.DisplayName,
                    AuthenticationScheme = x.Name
                }).ToList();

            var allowLocal = true;
            if (context?.ClientId != null)
            {
                var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
                if (client != null)
                {
                    allowLocal = client.EnableLocalLogin;

                    if (client.IdentityProviderRestrictions != null &amp;&amp; client.IdentityProviderRestrictions.Any())
                    {
                        providers = providers.Where(provider =&gt; client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
                    }
                }
            }

            return new LoginViewModel
            {
                AllowRememberLogin = AccountOptions.AllowRememberLogin,
                EnableLocalLogin = allowLocal &amp;&amp; AccountOptions.AllowLocalLogin,
                ReturnUrl = returnUrl,
                Email = context?.LoginHint,
                ExternalProviders = providers.ToArray()
            };
        }

        private async Task&lt;LoginViewModel&gt; BuildLoginViewModelAsync(LoginInputModel model)
        {
            var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
            vm.Email = model.Email;
            vm.RememberLogin = model.RememberLogin;
            return vm;
        }

        private async Task&lt;LogoutViewModel&gt; BuildLogoutViewModelAsync(string logoutId)
        {
            var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };

            if (User?.Identity.IsAuthenticated != true)
            {
                // if the user is not authenticated, then just show logged out page
                vm.ShowLogoutPrompt = false;
                return vm;
            }

            var context = await _interaction.GetLogoutContextAsync(logoutId);
            if (context?.ShowSignoutPrompt == false)
            {
                // it's safe to automatically sign-out
                vm.ShowLogoutPrompt = false;
                return vm;
            }

            // show the logout prompt. this prevents attacks where the user
            // is automatically signed out by another malicious web page.
            return vm;
        }

        private async Task&lt;LoggedOutViewModel&gt; BuildLoggedOutViewModelAsync(string logoutId)
        {
            // get context information (client name, post logout redirect URI and iframe for federated signout)
            var logout = await _interaction.GetLogoutContextAsync(logoutId);

            var vm = new LoggedOutViewModel
            {
                AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
                PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
                ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
                SignOutIframeUrl = logout?.SignOutIFrameUrl,
                LogoutId = logoutId
            };

            if (User?.Identity.IsAuthenticated == true)
            {
                var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
                if (idp != null &amp;&amp; idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
                {
                    var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
                    if (providerSupportsSignout)
                    {
                        if (vm.LogoutId == null)
                        {
                            // if there's no current logout context, we need to create one
                            // this captures necessary info from the current logged in user
                            // before we signout and redirect away to the external IdP for signout
                            vm.LogoutId = await _interaction.CreateLogoutContextAsync();
                        }

                        vm.ExternalAuthenticationScheme = idp;
                    }
                }
            }

            return vm;
        }

        private async Task&lt;IActionResult&gt; ProcessWindowsLoginAsync(string returnUrl)
        {
            // see if windows auth has already been requested and succeeded
            var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
            if (result?.Principal is WindowsPrincipal wp)
            {
                // we will issue the external cookie and then redirect the
                // user back to the external callback, in essence, testing windows
                // auth the same as any other external authentication mechanism
                var props = new AuthenticationProperties()
                {
                    RedirectUri = Url.Action(&quot;ExternalLoginCallback&quot;),
                    Items =
                    {
                        { &quot;returnUrl&quot;, returnUrl },
                        { &quot;scheme&quot;, AccountOptions.WindowsAuthenticationSchemeName },
                    }
                };

                var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
                id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
                id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));

                // add the groups as claims -- be careful if the number of groups is too large
                if (AccountOptions.IncludeWindowsGroups)
                {
                    var wi = wp.Identity as WindowsIdentity;
                    var groups = wi.Groups.Translate(typeof(NTAccount));
                    var roles = groups.Select(x =&gt; new Claim(JwtClaimTypes.Role, x.Value));
                    id.AddClaims(roles);
                }

                await HttpContext.SignInAsync(
                    IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
                    new ClaimsPrincipal(id),
                    props);
                return Redirect(props.RedirectUri);
            }
            else
            {
                // trigger windows auth
                // since windows auth don't support the redirect uri,
                // this URL is re-triggered when we call challenge
                return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
            }
        }

        private async Task&lt;(ApplicationUser user, string provider, string providerUserId, IEnumerable&lt;Claim&gt; claims)&gt; 
            FindUserFromExternalProviderAsync(AuthenticateResult result)
        {
            var externalUser = result.Principal;

            // try to determine the unique id of the external user (issued by the provider)
            // the most common claim type for that are the sub claim and the NameIdentifier
            // depending on the external provider, some other claim type might be used
            var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
                              externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
                              throw new Exception(&quot;Unknown userid&quot;);

            // remove the user id claim so we don't include it as an extra claim if/when we provision the user
            var claims = externalUser.Claims.ToList();
            claims.Remove(userIdClaim);

            var provider = result.Properties.Items[&quot;scheme&quot;];
            var providerUserId = userIdClaim.Value;

            // find external user
            var user = await _userManager.FindByLoginAsync(provider, providerUserId);

            return (user, provider, providerUserId, claims);
        }

        private async Task&lt;ApplicationUser&gt; AutoProvisionUserAsync(string provider, string providerUserId, IEnumerable&lt;Claim&gt; claims)
        {
            // create a list of claims that we want to transfer to our store
            var filtered = new List&lt;Claim&gt;();

            // user's display name
            var name = claims.FirstOrDefault(x =&gt; x.Type == JwtClaimTypes.Name)?.Value ??
                claims.FirstOrDefault(x =&gt; x.Type == ClaimTypes.Name)?.Value;
            if (name != null)
            {
                filtered.Add(new Claim(JwtClaimTypes.Name, name));
            }
            else
            {
                var first = claims.FirstOrDefault(x =&gt; x.Type == JwtClaimTypes.GivenName)?.Value ??
                    claims.FirstOrDefault(x =&gt; x.Type == ClaimTypes.GivenName)?.Value;
                var last = claims.FirstOrDefault(x =&gt; x.Type == JwtClaimTypes.FamilyName)?.Value ??
                    claims.FirstOrDefault(x =&gt; x.Type == ClaimTypes.Surname)?.Value;
                if (first != null &amp;&amp; last != null)
                {
                    filtered.Add(new Claim(JwtClaimTypes.Name, first + &quot; &quot; + last));
                }
                else if (first != null)
                {
                    filtered.Add(new Claim(JwtClaimTypes.Name, first));
                }
                else if (last != null)
                {
                    filtered.Add(new Claim(JwtClaimTypes.Name, last));
                }
            }

            // email
            var email = claims.FirstOrDefault(x =&gt; x.Type == JwtClaimTypes.Email)?.Value ??
               claims.FirstOrDefault(x =&gt; x.Type == ClaimTypes.Email)?.Value;
            if (email != null)
            {
                filtered.Add(new Claim(JwtClaimTypes.Email, email));
            }

            var user = new ApplicationUser
            {
                UserName = Guid.NewGuid().ToString(),
            };
            var identityResult = await _userManager.CreateAsync(user);
            if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);

            if (filtered.Any())
            {
                identityResult = await _userManager.AddClaimsAsync(user, filtered);
                if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
            }

            identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider));
            if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);

            return user;
        }

        private void ProcessLoginCallbackForOidc(AuthenticateResult externalResult, List&lt;Claim&gt; localClaims, AuthenticationProperties localSignInProps)
        {
            // if the external system sent a session id claim, copy it over
            // so we can use it for single sign-out
            var sid = externalResult.Principal.Claims.FirstOrDefault(x =&gt; x.Type == JwtClaimTypes.SessionId);
            if (sid != null)
            {
                localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
            }

            // if the external provider issued an id_token, we'll keep it for signout
            var id_token = externalResult.Properties.GetTokenValue(&quot;id_token&quot;);
            if (id_token != null)
            {
                localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = &quot;id_token&quot;, Value = id_token } });
            }
        }

        private void ProcessLoginCallbackForWsFed(AuthenticateResult externalResult, List&lt;Claim&gt; localClaims, AuthenticationProperties localSignInProps)
        {
        }

        private void ProcessLoginCallbackForSaml2p(AuthenticateResult externalResult, List&lt;Claim&gt; localClaims, AuthenticationProperties localSignInProps)
        {
        }
    }
}
</code></pre>
<p>Now when you run the MVC Client app, you should reach a home page which has no authentication requirements. When you click the &quot;secure&quot; tab, suddenly you hit a route with the [Authorise] property. The client will redirect to the Identity Server to log in. Once logged in, the Identity Server will then redirect you back to the MVC app with the correct credentials displaying the cookie. Success!</p>
<p>We have now implemented Server to Server authentication using a hybrid flow. This is not my usual project structure but it's a start towards my final aim of an API / Front End Authentication process. Part 2 will be on the process to authenticate an Angular 5+ front-end application.</p>
<p>The completed code for this post can be found <a href="https://github.com/SRAlexander/IS4-ASPUsers/tree/CreateASPUsers">here</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[HTTPS : I need a blogging platform!]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>No site these days is worth its worth unless HTTPS is enabled! This will guide you through the process of configuring your new blog to use HTTPS if you choose to do so. We will configure our domain to the server and then reverse proxy to the ghost blog container.</p>]]></description><link>http://tech.scottalex.com/i-need-a-blogging-platform-https/</link><guid isPermaLink="false">5c83b5653b595e0001a9d98d</guid><category><![CDATA[Ghost]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Azure]]></category><category><![CDATA[Nginx]]></category><category><![CDATA[Ubuntu 17.10]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Tue, 10 Apr 2018 10:07:15 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/04/n-CYBERSECURITY-628x314.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/04/n-CYBERSECURITY-628x314.jpg" alt="HTTPS : I need a blogging platform!"><p>No site these days is worth its worth unless HTTPS is enabled! This will guide you through the process of configuring your new blog to use HTTPS if you choose to do so. We will configure our domain to the server and then reverse proxy to the ghost blog container.</p>
<p>Firstly, you will need a domain. Head to any domain provider and pick up the domain you want. (I used <a href="https://names.co.uk">Names</a> since it was £5.99 at the time of writting this post for a .co.uk address and privacy protection). Once you've aquired your domain name, head into the DNS settings and set the A parmater to your VM's IP address. We won't need any further configurations at this point.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/04/Screen-Shot-2018-04-10-at-10.04.14.png" alt="HTTPS : I need a blogging platform!"></p>
<p>While the DNS tables update globally, we will head into our VM. SSH on to your machine. The first thing we will do is update our apt-get repo mirrors. By default, Azure has its own repos which, at the time of writing this post, were not active. To do this head to /etc/apt and open sources.list. If you don't have a text editor, run the following:</p>
<pre><code>sudo apt-get update
sudo apt-get install nano
sudo nano sources.list
</code></pre>
<p>Now, wherever Azure is referenced in the file, change it to &quot;us&quot;. To exit and save in nano, press ctrl+x which will prompt you to save on exiting. Then finally update one more time.</p>
<pre><code>sudo apt-get update
</code></pre>
<p>Good, we should now have access to the repos we require. We now need to install nginx which is the equivalant to IIS on Ubuntu 17.10.</p>
<pre><code>sudo apt-get install nginx
systemctl status nginx // Check nginx is up and running
</code></pre>
<p>Heading to http://{server ip} should now bring up a default nginx home page. Success!</p>
<p><strong>Certbot</strong></p>
<p>We will use Certbot to create a HTTPS certificate for us, follow these instructions to install Certbot.</p>
<pre><code>sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx
</code></pre>
<p>With that installed, we need to open up our default nginx settings to allow access to &quot;yourdomain.com/.well-known/acme-challenge&quot; so that we can confirm access through lets encrypt. There are mutiple ways to configure these settings but for now, this is what has worked for me. Head to /ect/nginx/sites-avaliable and open the file named default in nano. It's probably worth a read through this file to understand what options are avalaiable; once read update the file to the following:</p>
<pre><code>server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/yourdomain.com;
    location ^~ /.well-known/acme-challenge {
        allow all;
    }

    location / {
        return 301 https://$server_name$request_uri;
    }
}
</code></pre>
<p>Now we can make sure that there are no errors with the syntax and reload Nginx.</p>
<pre><code>sudo nginx -t
sudo systemctl reload nginx
</code></pre>
<p>You can see that root has been defined here, make sure this directory exists with the steps below. We will also test to make sure we have access to the relevant directories before running the certification process.</p>
<pre><code>cd /var/www
mkdir yourdomain.com
cd yourdomain.com
mkdir .well-known
cd .well-known
mkdir acme-challenge
cd acme-challenge
touch testfile.txt
sudo nano testfile.txt
hello world
ctrl + x
</code></pre>
<p>As soon as the global DNS tables have been updated, you can test to see if the file is downloadable by accessing <a href="http://yourdomain.com/.well-known/acme-challenge/testfile.txt">http://yourdomain.com/.well-known/acme-challenge/testfile.txt</a> or if it's still going through http://{serverip}/.well-known/acme-challenge/testfile.txt.</p>
<p>Once successful, head back to /var/www/yourdomain.com and remove the /.well-known folder.</p>
<pre><code>rm -r .well-known
</code></pre>
<p>The certification process will create the directories itself. For the next step, the DNS tables must be working so you may have to wait at this point. To check the status of the update head to <a href="https://www.whatsmydns.net">Whats my DNS</a> and type in &quot;yourdomain.com&quot; to see if it's pointing to your machine.</p>
<p><strong>Get the Certificate</strong></p>
<p>The Certbot has become a lot more user friendly recently and will update the nginx configuratiom file diretly with the --nginx flag. Nice! To do so run:</p>
<pre><code>sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
</code></pre>
<p>Follow the instructions providing your domain and the folder that we created above and it will grant us with a certificate for our HTTPS. The certificate will have to be updated every 90 days which can be automated but for now let's just simulate the update process to make sure everything is in order.</p>
<pre><code>sudo certbot renew --dry-run
</code></pre>
<p><strong>Open the ports</strong></p>
<p>HTTPS/SSL uses port 443 which we have not opened up yet on Azure. Head to your Azure portal &gt; then to the VM &gt; then to the Networking tab. Add in a new inbound port rule for port 443 to open up access to https://.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/04/Screen-Shot-2018-04-10-at-10.45.21.png" alt="HTTPS : I need a blogging platform!"></p>
<p>Next we want to direct our server to our Ghost container instance. Head back to /ect/nginx/sites-avaliable and open the default file in nano. In the server settings for the new server listening on 443 created by Certbot, set &quot;location /&quot; inner brackets to:</p>
<pre><code>proxy_pass http://127.0.0.1:&lt;ghost container port&gt;
proxy_set_header    X-Real-IP $remote_addr;
proxy_set_header    Host      $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
</code></pre>
<p>We're also going to redirect the http route directly to HTTPS. For the original server that is listening on port 80, we want to add the following return statement:</p>
<pre><code>return 301 https://yourdomain.com.
</code></pre>
<p>Check over the config file and reload it.</p>
<pre><code>sudo nginx -t
sudo systemctl reload nginx
</code></pre>
<p>Now try heading to <a href="https://yourdomain.com">https://yourdomain.com</a> and <a href="http://yourdomain.com">http://yourdomain.com</a>. You should now have access and the http route should head directly to the https route!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[I need a blogging platform!]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>So, it's come to a point where I need to separate out my blogs into their respective topics and document the results of my projects for sharing purposes. So, welcome to my first post on my tech blog which will provide a brief overview of Azure VMs, Docker and the</p>]]></description><link>http://tech.scottalex.com/i-need-a-blogging-platform/</link><guid isPermaLink="false">5c83b5653b595e0001a9d98c</guid><category><![CDATA[Ghost]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Azure]]></category><dc:creator><![CDATA[Scott Alexander]]></dc:creator><pubDate>Sat, 07 Apr 2018 20:00:32 GMT</pubDate><media:content url="http://tech.scottalex.com/content/images/2018/04/ghost-blogging-platform_abzrkg.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://tech.scottalex.com/content/images/2018/04/ghost-blogging-platform_abzrkg.jpg" alt="I need a blogging platform!"><p>So, it's come to a point where I need to separate out my blogs into their respective topics and document the results of my projects for sharing purposes. So, welcome to my first post on my tech blog which will provide a brief overview of Azure VMs, Docker and the Ghost blogging platform.</p>
<p>I've used the Ghost blogging platform previously (before it reached v1) and remembered how simple its interface is in terms of blog theming, markdown and content management so thought let's combine it with Docker to get it off the ground fast. So what will we be using?</p>
<ul>
<li><a href="https://ghost.org">Ghost</a></li>
<li><a href="https://www.docker.com">Docker</a></li>
<li><a href="https://portal.azure.com">Azure</a> (You will need an account)</li>
</ul>
<p><strong>VM Setup</strong></p>
<p>The first thing to do is to spin up an instance of a Ubuntu Server on Azure, the process is fairly straightforward; select create a resource and search for Ubuntu Server 17.10 VM. Follow the steps through with the following in mind:</p>
<ul>
<li>Choose a machine that your credit covers</li>
<li>When you get to configuring the IP, make sure it's set to static otherwise redirecting a domain to the blog is not going to be possible.</li>
</ul>
<p><img src="http://tech.scottalex.com/content/images/2018/04/azure-Ip-Config.PNG" alt="I need a blogging platform!"></p>
<p>Why use a Ubuntu Server instead of MS Server 2016? Well, the Docker containers as a preference run on Linux so if a container does not have a Docker for Windows configuration, we can't use it. There also seems to be some issues with HyperV on Azure when it comes to virtualising a Linux box for Docker on Windows so we can't use Linux containers on Windows Server 2016. Bummer!</p>
<p>Right, once the machines up and running we need to ssh onto the machine. Grab a copy of <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">Putty</a> if you don't already have it. Open a command line terminal at its location and type...</p>
<pre><code>putty.exe -ssh {username}@{machine ip address}
(Note if SSH is blocked on your machine you can run this from another vm)
</code></pre>
<p>You will then get prompted for your password after which you'll then be connected to your VM.</p>
<p><strong>Docker Setup</strong></p>
<p>The next step is to set up Docker which fortunately Digital Ocean has provided a nice <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-16-04">guide</a>. Right now we're only interested in step one and two which consist of the following commands:</p>
<p>Install Docker</p>
<pre><code>$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;
$ sudo apt-get update
$ apt-cache policy docker-ce
$ sudo apt-get install -y docker-ce
$ sudo systemctl status docker // Should indicate that Docker is installed
</code></pre>
<p>Configure Docker to bypass sudo</p>
<pre><code>$ sudo usermod -aG docker ${USER} //Where user is your machine login
$ su - ${USER}
$ (Input password)
$ id -nG // will confirm that docker has been configured.
</code></pre>
<p>At this point I would also restart the VM to make sure the changes take effect. Good news - we are now ready to set up Ghost.</p>
<p><strong>Installing Ghost</strong></p>
<p>Fortunately for us, the Ghost team have already set up a Ghost container so let's pull it.</p>
<pre><code>$ docker pull ghost // Will default to @latest
</code></pre>
<p>That's it, we have everything we need. All we need to do now is start the container with the following:</p>
<pre><code>$ docker run -d --restart unless-stopped --name {choose a name} -p {choose a port}:2368 ghost
</code></pre>
<p>This will run the container as a background process (-d), it will restart until manually stopped (--restart unless-stopped), perfect for when the VM restarts. --name is how you will reference the container in Docker and the port (-p) is how you will access the blog e.g. <a href="http://localhost">http://localhost</a>:{chosen port }.</p>
<p><strong>Open the Port</strong></p>
<p>The final step is to open access to the port in Azure. In the VMs menu, select networking and add a new inbound port rule. Set port range to the specific port you defined earlier and set priority to 100 and click OK.</p>
<p><img src="http://tech.scottalex.com/content/images/2018/04/Screen-Shot-2018-04-07-at-21.15.55--1-.png" alt="I need a blogging platform!"></p>
<p>Once Azure is done with that, you should now be able to access your blog through http://{VM ip}:{chosen port}. Nice!</p>
<p>You now have a Ghost blogging platform while your VM is running. To get to the blog manager just head to http://{VM ip}:{chosen port}/ghost which will get you to create an account. Following that, you can modify the content until your heart's content. Happy Blogging.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>