Showing posts with label ASP.NET. Show all posts
Showing posts with label ASP.NET. Show all posts

Social Authentication in ASP.NET Core MVC

A common modern convenience for apps is the ability to choose to authenticate and create an account based on credentials from an existing service that most people have (ie. Google, Twitter, LinkedIn, GitHub, etc.). in this post I will walk through the configuration of social authentication for Google and LinkedIn accounts.

Setting up social authentication in ASP.NET Core is a lot easier than you might think...

Before implementing this feature you will need to register for a Google and a LinkedIn developer account which will then give you access to the 2 values that make the magic of this built-in authentication possible: clientKey and clientSecret.

Important(!): you will also want to register the Callback/Redirect URLs for each social authentication as shown below. This redirect URL below is for my project running on localhost:44396 obviously ("/signin-google" is the path you want to append to your app root for Google and "/signin-linkedin" for LinkedIn):


Once you have this setup, you will be able to wire them up in the Startup.cs ConfigureServices() method of an ASP.NET Core 3 MVC Web Application ie:

  public void ConfigureServices(IServiceCollection services)  
     {  
       services.AddDbContext<ApplicationDbContext>(options =>  
         options.UseSqlServer(  
           Configuration.GetConnectionString("DefaultConnection")));  
       services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)  
         .AddEntityFrameworkStores<ApplicationDbContext>();  
       services.AddControllersWithViews();  
       services.AddRazorPages();  
       services.AddAuthentication()  
       .AddGoogle(o =>  
       {  
         o.ClientId = "[YourGoogleClientId]";  
         o.ClientSecret = "[YourGoogleClientSecret]";  
       })  
       .AddLinkedIn(o =>  
       {  
         o.ClientId = "[YourLinkedInClientId]";  
         o.ClientSecret = "[YourLinkedInClientSecret]";  
       });  
       // Add application services.   
       services.AddMvc();  
     }  


Now you just have to:

  • Create your ASP.NET Core 3 MVC Web Application project with Identity options as below
  • Install a couple Nuget packages for Google OAuth2 and Linked OAuth respectively
  • Call "Update-Database" from Nuget Package Manager Console
  • Modify Startup.cs

To implement this feature for your users you only need follow a couple steps when setting up the project (whether ASP.NET MVC or the newer ASP.NET Core MVC) to enable some builtin identify and authentication handling after which you can configure and customize to fit your app's particular custom authentication needs. Follow the images below for proper installation of the required 2 Nuget packages, the db command, etc.

First, make sure you create your ASP.NET Core MVC project with "Individual User Accounts" radio button selection and "Store user accounts in app" dropdown selection as follows:


Change Authentication to use Individual User Accounts- this sets up the required boilerplate template code



You will add this Nuget package for LinkedIn authentication



You will add this Nuget package for Google authentication


Next you will need to create the database objects (ASPNET app auth db and tables) via "Update-Database" Package Manager Console command


Finally, modify Startup.cs ConfigureServices() method as shown in the snippet above. Compile and give it a whirl.

And that is all there is to it. Strangely there is not a lot of documentation on exactly how to do this and what can cause it to not work; I spent over an hour debugging what turned out to be a not-so-obvious issue (in my case I was following instructions that erroneously suggested I inject unneeded services to the services.AddAuthentication() instantiation in the snippet of Startup.cs above).


If your keys and Callback URLs are correct you will be able to authenticate to Google and LinkedIn



Once you have registered your social account, you can then log in with it and you will see the following (notice "Hello!...." and "Logout")


If you are like me and do not want to keep to adding yet more and more credentials to LastPass, your users probably don't either. Implementing Social Authentication is a powerful tool that you can leverage with relative ease in ASP.NET Core 3 MVC Web apps.

If you have questions on implementing this or just need some help and general guidance, feel free to comment or drop me an email at colin@sonrai.io


GitHub: https://github.com/cfitzg/DotNetSocialAuth


References:

http://codereform.com/blog/post/asp-net-core-2-1-authentication-with-social-logins/

https://docs.microsoft.com/en-us/aspnet/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on

https://ankitsharmablogs.com/authentication-using-linkedin-asp-net-core-2-0/  

https://stackoverflow.com/questions/53654020/how-to-implement-google-login-in-net-core-without-an-entityframework-provider


TwickrTape

Real-time scrolling Tweets related to financial news alerts and updates. Just enter the Twitter handle you want to see a marquee of live streaming tweets from.

TL;DR - the working app is here: https://twickrtape.azurewebsites.net
..znd here on Google Play Store: https://play.google.com/store/apps/details?id=io.cordova.twicktape

Use button in lower-left to toggle different Twitter handles

The code is a hodgepodge of various references and vestiges of past personal projects. My initial aim was to get something working end-to-end as I envisioned, and I was able to achieve that pretty easily through the Twitter API and .NET.

The process is standard API stuff- first you authenticate and receive a token which you can then subsequently pass to prove that your requests are valid. Then, using simple (REST API) HTTP GET, we get the content (Twitter Timeline information) that we are interested in.

The gist of the code is .NET C# (an ASP.NET Controller method) and listed below:

  public ActionResult GetTwickr(string handle = "business")  
     {  
       // Set your own keys and screen name  
       var oAuthConsumerKey = "XXXXXXXXXXXXX"; // "API key";  
       var oAuthConsumerSecret = "XXXXXXXXXXXXXXXXXXXXX"; // "API secret key";  
       var oAuthUrl = "https://api.twitter.com/oauth2/token";  
       var screenname = "@" + handle; // default Twitter display current status  
       // Authenticate  
       var authHeaderFormat = "Basic {0}";  
       var authHeader = string.Format(authHeaderFormat,  
         Convert.ToBase64String(Encoding.UTF8.GetBytes(Uri.EscapeDataString(oAuthConsumerKey) + ":" +  
         Uri.EscapeDataString((oAuthConsumerSecret)))  
       ));  
       var postBody = "grant_type=client_credentials";  
       HttpWebRequest authRequest = (HttpWebRequest)WebRequest.Create(oAuthUrl);  
       authRequest.Headers.Add("Authorization", authHeader);  
       authRequest.Method = "POST";  
       authRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";  
       authRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;  
       using (Stream stream = authRequest.GetRequestStream())  
       {  
         byte[] content = ASCIIEncoding.ASCII.GetBytes(postBody);  
         stream.Write(content, 0, content.Length);  
       }  
       authRequest.Headers.Add("Accept-Encoding", "gzip");  
       WebResponse authResponse = authRequest.GetResponse();  
       // deserialize into an object  
       TwitAuthenticateResponse twitAuthResponse;  
       using (authResponse)  
       {  
         using (var reader = new StreamReader(authResponse.GetResponseStream()))  
         {  
           System.Web.Script.Serialization.JavaScriptSerializer js = new System.Web.Script.Serialization.JavaScriptSerializer();  
           var objectText = reader.ReadToEnd();  
           twitAuthResponse = JsonConvert.DeserializeObject<TwitAuthenticateResponse>(objectText);  
         }  
       }  
       try  
       {  
         // Get timeline info  
         var timelineFormat = "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={0}&include_rts=1&exclude_replies=1&count=5";  
         var timelineUrl = string.Format(timelineFormat, screenname);  
         HttpWebRequest timeLineRequest = (HttpWebRequest)WebRequest.Create(timelineUrl);  
         var timelineHeaderFormat = "{0} {1}";  
         timeLineRequest.Headers.Add("Authorization", string.Format(timelineHeaderFormat, twitAuthResponse.token_type, twitAuthResponse.access_token));  
         timeLineRequest.Method = "GET";  
         WebResponse timeLineResponse = timeLineRequest.GetResponse();  
         var timeLineJson = string.Empty;  
         string scrolltxt = string.Empty;  
         using (timeLineResponse)  
         {  
           using (var reader = new StreamReader(timeLineResponse.GetResponseStream()))  
           {  
             timeLineJson = reader.ReadToEnd();  
           }  
         }  
         // deserialize into an object  
         dynamic obj = JsonConvert.DeserializeObject(timeLineJson);  
         foreach (var r in obj.Root)  
         {  
           scrolltxt += " ***** " + r.text;  
         }  
         var model = new LoggedInUserTimelineViewModel { TimelineContent = scrolltxt, Handle = handle };  
         return View("TwickrMain", model);  
       }  
       catch (Exception e) { }  
       return View("TwickrMain");  
     }  

Next, using Newtonsoft,Json JsonConvert() function, we deserialize the JSON response and zero in on the 'text' property of each entity (tweet) in the array of JSON results (tweets).

And finally, using some basic Bootstrap HTML, JavaScript and CSS we are able to wire up a very simple, but effective app that interacts with the Twitter API in real-time.

Reference: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline.html

Generate Secure Machine Key Section for Web.config via PowerShell

Machine Keys are used in ASP.NET for securing machines that are part of a web farm as well as for sharing encrypted application session and state information.

This PowerShell script (function) can be called (once run and saved to your PS session) via,

"PS C:\: Generate-MachineKey"


With the output from this PS function, you can copy and paste to your web.config ie:
 <configuration>  
  <system.web>  
   <machineKey ... />  
  </system.web>  
 </configuration>  

Generate-MachineKey function definition/PS script
 # Generates a <machineKey> element that can be copied + pasted into a Web.config file.  
 function Generate-MachineKey {  
  [CmdletBinding()]  
  param (  
   [ValidateSet("AES", "DES", "3DES")]  
   [string]$decryptionAlgorithm = 'AES',  
   [ValidateSet("MD5", "SHA1", "HMACSHA256", "HMACSHA384", "HMACSHA512")]  
   [string]$validationAlgorithm = 'HMACSHA256'  
  )  
  process {  
   function BinaryToHex {  
     [CmdLetBinding()]  
     param($bytes)  
     process {  
       $builder = new-object System.Text.StringBuilder  
       foreach ($b in $bytes) {  
        $builder = $builder.AppendFormat([System.Globalization.CultureInfo]::InvariantCulture, "{0:X2}", $b)  
       }  
       $builder  
     }  
   }  
   switch ($decryptionAlgorithm) {  
    "AES" { $decryptionObject = new-object System.Security.Cryptography.AesCryptoServiceProvider }  
    "DES" { $decryptionObject = new-object System.Security.Cryptography.DESCryptoServiceProvider }  
    "3DES" { $decryptionObject = new-object System.Security.Cryptography.TripleDESCryptoServiceProvider }  
   }  
   $decryptionObject.GenerateKey()  
   $decryptionKey = BinaryToHex($decryptionObject.Key)  
   $decryptionObject.Dispose()  
   switch ($validationAlgorithm) {  
    "MD5" { $validationObject = new-object System.Security.Cryptography.HMACMD5 }  
    "SHA1" { $validationObject = new-object System.Security.Cryptography.HMACSHA1 }  
    "HMACSHA256" { $validationObject = new-object System.Security.Cryptography.HMACSHA256 }  
    "HMACSHA385" { $validationObject = new-object System.Security.Cryptography.HMACSHA384 }  
    "HMACSHA512" { $validationObject = new-object System.Security.Cryptography.HMACSHA512 }  
   }  
   $validationKey = BinaryToHex($validationObject.Key)  
   $validationObject.Dispose()  
   [string]::Format([System.Globalization.CultureInfo]::InvariantCulture,  
    "<machineKey decryption=`"{0}`" decryptionKey=`"{1}`" validation=`"{2}`" validationKey=`"{3}`" />",  
    $decryptionAlgorithm.ToUpperInvariant(), $decryptionKey,  
    $validationAlgorithm.ToUpperInvariant(), $validationKey)  
  }  
 }  

Visual Studio Config Transforms

When you are working on software that needs to target different resources for different environments (ie. local, DEV, STAGE, PRODUCTION), you can easily manage your VS project environment through the use of .config transformation files.

Here is your default web.config:



Simply make an alternate version (with the same name) of the element you wish to change in your various configuration transformations (web.DEBUG.config, web.Development.config, etc.). In this example, we are setting up a transform to change the connection string that has the name, "applicationConnStr":



NOTE: The same configuration element (the same name, specifically) you want to transform must be in the base web.config file.

By default, the transformation we have set up will only be applied when the project is published. In order to get the transformations working with each build, you must edit the bottom of the *.csproj file with the following Target:

  <Target Name="BeforeBuild">
    <TransformXml 
        Source="Web.Base.config" 
        Transform="Web.$(Configuration).config" 
        Destination="Web.config" />
  </Target>



And be sure to uncomment the code, it is commented out by default. Now select a different build configuration, build your project, and you should see the "applicationConnStr" transformed to match the element from the web.config.[Debug||Release||etc.] that you selected to Build:




To add new web.config transform files, just right click on the web.config file and select "Add Config Transformation".



To associate your transformations with different builds, under the Build Dropdown, select "Configuration Manager..." and configure as you like:




Setting up build configurations that point to different web.configs (actually web.config "transforms") allows you to debug and run code according to your needs (ie. show DEMO at a meeting, Debug Development to uncover a bug, point to local for ongoing development, etc.).

ASP.NET Core has a very different configuration setup that uses JSON objects and more configurable things in .NET Core Startup.cs; this reference is for .NET Standard.

The key in all of this is the name of the element you are matching and then replacing. It just has to match in both places and all will be transformed on your application's web.config. In this example, we matched and replaced the value for a config element with the name, "applicationConnStr".

In a real-world scenario, there are many things that we could dynamically replace through config transforms: database connection strings, image servers, mail servers, file shares, printer locations, encryption keys, etc.).

Reference: https://docs.microsoft.com/en-us/aspnet/web-forms/overview/deployment/visual-studio-web-deployment/web-config-transformations