Progressive Web Apps (PWAs)

As an introduction to PWAs, let’s build a mobile app the fastest, easiest way, using the Progressive Web App model. A PWA is both a mobile app and a website. Built with HTML, CSS, and javascript, the website can be viewed as either a website on your laptop or a mobile app on your phone or tablet. No need to develop a separate native app for every platform!

Creating Website with Addition of JavaScript files app will behave "app-like"

What does “App-like” mean? PWAs can work on your phone or tablet so much like a native app, it is hard to see the difference. Twitter produces both, a native app you can find in your app store, and a PWA you can “install” from browsing to twitter.com. Check out images of each:

Natie iPhone App vs. PWA - Twitter.com

A PWA can even access device hardware like the camera or microphone! They can produce push notifications and work offline. How does it do these things? It’s all in the site javascript and structure. It is the best way to build a mobile app. Let’s see how this works.

PWA Goals

To work like a native app, the PWA website has to:

  • Work offline
  • Perform fast
  • A display like a native app, responsive, with no browser UI visible
  • Startup from an icon on your home screen

Advantages

Why and when would we want to create a PWA?

  • Fast deployment: Avoids submission to the app stores for review and approval
  • Quicker updates are published immediately because it is served on the web and so no need to wait for the user to download to their device
  • More discoverable: Users can find and link to it via search engines
  • Secure: They use the browser security model, so providing the security of operating within the web browser context
  • Connectivity independent: PWAs can function on very low bandwidth or no connection at all.
  • Installable: Like a mobile app, this is opened from an icon on the home screen.
  • Notifications: Can send push notifications to the device like a native app
  • Hardware access: Can use the mobile device camera, microphone, etc.

Progressive?

“Progressive” in the name refers to that it will work and display in all browsers to a varying degree. Because it is browser dependent, it is affected by the capability of which browser the user has, and what version. PWAs have a goal to display and function at least minimally in all browsers, regardless of version. As such they become progressively more feature-rich in newer versions.

History

The Progressive Web App concept was created by Google in 2015. Its roots go even further back though, to Steve Jobs’ introduction of the iPhone in 2007. He declared back then that development for the iPhone would be done in html5 and have no SDK. His vision was undermined by iPhone jailbreaking which forced Apple to retreat to having an SDK and app store. But the concept lived on and is seeing increasing life in the PWA model.

Let’s Build One

We will look at how to use a manifest and service worker to create a very simple application that provides the features of a PWA. This is exciting to see on your phone, in action!
In your development environment, create a folder in your local for your project, name it anything, and add subfolders for js and images, and an index.html file:

Application Providing Features of a PWA

This demo was edited VS Code and the site was served on IIS in Windows but any favorite editor and dev web server will be fine. Node.js http-server is another easily available web server you could use. The file index.html should look like this, to start. We’ll add more later:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Hello My Phone</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body class="fullscreen">
  <div class="container">
    <h1 class="title">Hello My Phone!</h1>
  </div>
</body>
</html>

Run the app now in your webserver to see how it is shaping up. So far this is just a web app. Push it to your GitHub Pages repo to see it run on the web. To add mobile app functionality, we need to add two files, a Service Worker and a manifest.

Service Worker and Manifest

These change the app from a simple web app to one that can behave like a mobile app, a Progressive Web App. The Service Worker is the middleman between the web page and content. It contains the code to determine whether to deliver static, cached content or dynamic data based on whatever criteria you want, typically whether it finds a connection to the internet or not. The manifest is a json file that provides properties to the browser such as which page to load when the app is first opened, or what is the title of the app. Both the manifest and the service worker live on the root directory for access.

Create a file named manifest.json in the root of the site, and add the following:

{
  "name": "My Phone App",
  "short_name": "My App",
  "icons": [{
    "src": "images/phoneapp-icon-128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, {
      "src": "images/phoneapp-icon-144.png",
      "sizes": "144x144",
      "type": "image/png"
    }, {
      "src": "images/phoneapp-icon-152.png",
      "sizes": "152x152",
      "type": "image/png"
    }, {
      "src": "images/phoneapp-icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    }, {
      "src": "images/phoneapp-icon-256.png",
      "sizes": "256x256",
      "type": "image/png"
    }, {
      "src": "images/phoneapp-icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }],
  "description": "Sample Progressive Web App",
  "lang": "en-US",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "white",
  "orientation": "any",
  "theme_color": "white"
}

Link the manifest file in your index.html file, in the head section:

&lt;link rel="manifest" href="/manifest.json"&gt;

Service Worker

To add the service worker, create a file in the root folder named sw.js. Add the following to it:

var cacheName = 'phoneApp';
var filesToCache = [
  './',
  './index.html',
  './css/style.css',
  './js/main.js'
];

/* Start the service worker and cache files in filesToCache */
self.addEventListener('install', function(e) {
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(filesToCache); 
    })
  );
});

/* Serve cached content when offline */
self.addEventListener('fetch', function(e) {
  e.respondWith(
    caches.match(e.request).then(function(response) {
      return response || fetch(e.request);
    })
  );
});

Adding a service worker is the first step towards enabling the following progressive web app features:

  • Offline performance
  • Push notifications
  • Add icon to the home screen

Our service worker file caches specifically named pages to be able to serve them from the cache when the device is offline. The file must be placed in the app root to allow it access to all of the app’s files. Service workers only have permission to access files in their same-level directory and subfolder. In the first function of the sw.js, the first item in the variable filesToCache is “./” because that allows caching of our index.html. The cacheName variable adds the content to the browser cache to be available to be called from the javascript. The following function fetches the cached content. This file works for our demo but to properly fortify the app with error handling and to see what else the service worker can do, check out PWABuilder. It is a handy file generator for your PWA, but even if you don’t use the generator, just looking at this site sums nicely what you can define in the manifest and service worker and what they can do. For now, we’ll keep our service worker simple.

Register the Service Worker

We need one more file to call, or “register”, the service worker. Create a javascript file in your js folder named main.js. This tells the browser where to find your service worker JavaScript file. Add the following:

window.onload = () =&gt; {
‘use strict’;

if (‘serviceWorker’ in navigator) {
navigator.serviceWorker
.register(‘./sw.js’);
}
}

Link to main.js at the bottom of index.html by adding a script link before the closing body tag:

<script src="js/main.js"></script>

App Icons

We need to provide icons that various displays can use, to support the mobile app experience of having an app icon on the device. Images of varying sizes are defined in the manifest above. We will put these images in the Images folder to be available to display the home screen icon when users “install” your app. Sample images for these and for favicon.ico are included in the downloadable files (link below). The file structure looks like this now:

Production of App Icons

Add Responsive HTML

To see the best effect of your sites’ web vs mobile view, add some responsive elements and styles to index.html. This example uses the free W3 Schools templates. It links to their online w3.css stylesheet for responsive styles. A link to the W3 Schools templates is included in the links at the end of this post along with a link to download the project files.

With the addition of the html below, our site now has navigation and layout that changes based on the viewport. The HTML now looks like this:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>PWA Sample</title>
  <link rel="manifest" href="manifest.json">
  <link rel="stylesheet" href="css/style.css"> <!-- optional -->
  <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="theme-color" content="white"/>
  <link rel="icon" href="favicon.ico" type="image/x-icon" />  
<link rel="apple-touch-icon" href="images/pwa-icon-152.jpg">   
<meta name="theme-color" content="white"/>  
<meta name="apple-mobile-web-app-capable" content="yes">  
<meta name="apple-mobile-web-app-status-bar-style" content="black"> 
<meta name="apple-mobile-web-app-title" content="Hello World"> 
<meta name="msapplication-TileImage" content="images/pwa-icon-144.jpg">  
<meta name="msapplication-TileColor" content="#FFFFFF">
</head>
<body class="fullscreen">
<!-- Sidebar/menu -->
<nav class="w3-sidebar w3-collapse w3-top w3-large w3-padding" style="z-index:3;width:300px;font-weight:bold; background-color:#ddd;" id="mySidebar">
  <a href="javascript:void(0)" onclick="w3_close()" class="w3-button w3-hide-large w3-display-topleft" style="width:100%;font-size:22px">Close Menu</a>
<div class="w3-container">
<div style="width: 120px; height:119px; background-color: #fff;">
  <img src="images/logo.jpg"</div>
</div>
<div class="w3-bar-block">
    <a href="#" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Home</a> 
    <a href="#sectionOne" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Section One</a> 
    <a href="#sectionTwo" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Section Two</a> 
    <a href="#simpleForm" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Contact</a>
  </div>
</nav>
<!-- Top menu on small screens -->
<header class="w3-container w3-top w3-hide-large w3-grey w3-xlarge w3-padding">
  <a href="javascript:void(0)" class="w3-button w3-grey w3-margin-right" onclick="w3_open()">☰</a>
  <span>My Mobile App</span>
</header>

<!-- Overlay effect when opening sidebar on small screens -->
<div class="w3-overlay w3-hide-large" onclick="w3_close()" style="cursor:pointer" title="close side menu" id="myOverlay"></div>
<!-- !PAGE CONTENT! -->
<div class="w3-main" style="margin-left:340px;margin-right:40px">
<!-- Header -->
  <div class="w3-container" id="showcase">
   <h1 class="w3-xxlarge">My Mobile App</h1>
   <h1 class="w3-large w3-text-gray"><b>Lorem dolor etc!</b></h1>
  </div>
<!-- Photo grid (modal) -->
<div class="w3-row-padding">
<div class="w3-half">
      <img src="images/circle1.jpg" style="width:100%" onclick="onClick(this)" alt="It's a photo">
      <img src="images/circle4.jpg" style="width:100%" onclick="onClick(this)" alt="It's a photo">
  </div>
<div class="w3-half">
      <img src="images/circle3.jpg" style="width:100%" onclick="onClick(this)" alt="It's a photo">
      <img src="images/circle2.jpg" style="width:100%" onclick="onClick(this)" alt="It's a photo">
  </div>
</div>
  <!-- Top Section -->
<div class="w3-container" id="sectionOne">
<h1 class="w3-large w3-text-gray"><b>Section One</b></h1>
  Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</div>
<!-- Section of Text -->
<div class="w3-container" id="sectionTwo">
<h1 class="w3-large w3-text-grey"><b>Section Two</b></h1>
 Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
<b>All The Things:</b>
  </div>
  <!-- Images in Boxes -->
<div class="w3-row-padding w3-grayscale">  
  <div class="w3-col m4 w3-margin-bottom"> 
   <div class="w3-light-grey">     
     <div class="w3-container">
        <h3>Thing 1</h3>Thing 1 Phasellus eget enim eu lectus faucibus vestibulum. Suspendisse sodales pellentesque elementum.
     </div>
   </div>
</div>
<div class="w3-col m4 w3-margin-bottom">
  <div class="w3-light-grey">
    <div class="w3-container">
       <h3>Thing 2</h3>Thing 2 Phasellus eget enim eu lectus faucibus vestibulum. Suspendisse sodales pellentesque elementum.
    </div>
  </div>
</div>
<div class="w3-col m4 w3-margin-bottom">
   <div class="w3-light-grey">
     <div class="w3-container">
       <h3>Thing 3</h3>Thing 3 Phasellus eget enim eu lectus faucibus vestibulum. Suspendisse sodales pellentesque elementum.
     </div>
    </div>
  </div>
</div>
<div class="w3-container" id="simpleForm" style="margin-top:75px">
My Form   
   <form action="/myformaction.js" target="_blank">
     <div class="w3-section">
      <label>Name</label>
      <input class="w3-input w3-border" type="text" name="Name" required>
     </div>
     <div class="w3-section">
       <label>Email</label>
       <input class="w3-input w3-border" type="text" name="Email" required>
     </div>
     <div class="w3-section">
       <label>Message</label>
       <input class="w3-input w3-border" type="text" name="Message" required>
     </div>
     <button type="submit" class="w3-button w3-block w3-padding-large w3-grey w3-margin-bottom">Send Message</button>
   </form>
</div>
<!-- End page content -->
</div>
<script>
// Script to open and close sidebar
function w3_open() {
  document.getElementById("mySidebar").style.display = "block";
  document.getElementById("myOverlay").style.display = "block";
}

function w3_close() {
  document.getElementById("mySidebar").style.display = "none";
  document.getElementById("myOverlay").style.display = "none";
}
</script>
<script src="js/main.js"></script>
</body>
</html>

Install On Your Phone

Upload the files to a WebHost or browse to the localhost IP address in your wifi network, to try it on your phone. On the iPhone, browse to the URL and then choose the share button.

iOS Share button

From the context menu, choose “Add to home screen”. Android devices make this even easier by displaying a default button. See links at the end of this post for options to install on Android and other devices.

Summary

Progressive Web Apps offer the best of native mobile apps combined with the advantages of web development. Over time, the increasing embrace of PWA techniques by developers could bring Steve Jobs’ vision for smartphone applications to reality: no need for phone SDKs or and native app deployments or app store constraints, and a world in which web technology can be used to create mobile apps that are faster to build with every bit of functionality equal to their native counterparts.

Links

More about installation of a PWA on other OSs:
Accurately identifying and authenticating users is an essential requirement for any modern application. As modern applications continue to migrate beyond the physical boundaries of the data center and into the cloud, balancing the ability to leverage trusted identity stores with the need for enhanced flexibility to support this migration can be tricky. Additionally, evolving requirements like allowing multiple partners, authenticating across devices, or supporting new identity sources push application teams to embrace modern authentication protocols.

Microsoft states that federated identity is the ability to “Delegate authentication to an external identity provider. This can simplify development, minimize the requirement for user administration, and improve the user experience of the application.”

As organizations expand their user base to allow authentication of multiple users/partners/collaborators in their systems, the need for federated identity is imperative.

The Benefits of Federated Authentication

Federated authentication allows organizations to reliably outsource their authentication mechanism. It helps them focus on actually providing their service instead of spending time and effort on authentication infrastructure. An organization/service that provides authentication to their sub-systems are called Identity Providers. They provide federated identity authentication to the service provider/relying party. By using a common identity provider, relying applications can easily access other applications and web sites using single sign on (SSO).

SSO provides quick accessibility for users to multiple web sites without needing to manage individual passwords. Relying party applications communicate with a service provider, which then communicates with the identity provider to get user claims (claims authentication).

For example, an application registered in Azure Active Directory (AAD) relies on it as the identity provider. Users accessing an application registered in AAD will be prompted for their credentials and upon authentication from AAD, the access tokens are sent to the application. The valid claims token authenticates the user and the application does any further authentication. So here the application doesn’t need to have additional mechanisms for authentication thanks to the federated authentication from AAD. The authentication process can be combined with multi-factor authentication as well.

Glossary

Abbreviation Description
STS Security Token Service
IdP Identity Provider
SP Service Provider
POC Proof of Concept
SAML Security Assertion Markup Language
RP Relying party (same as service provider) that calls the Identity Provider to get tokens
AAD Azure Active Directory
ADDS Active Directory Domain Services
ADFS Active Directory Federation Services
OWIN Open Web Interface for .NET
SSO Single sign on
MFA Multi factor authentication

OpenId Connect/OAuth 2.0 & SAML

SAML and OpenID/OAuth are the two main types of Identity Providers that modern applications implement and consume as a service to authenticate their users. They both provide a framework for implementing SSO/federated authentication. OpenID is an open standard for authentication and combines with OAuth for authorization. SAML is also open standard and provides both authentication and authorization.  OpenID is JSON; OAuth2 can be either JSON or SAML2 whereas SAML is XML based. OpenID/OAuth are best suited for consumer applications like mobile apps, while SAML is preferred for enterprise-wide SSO implementation.

Microsoft Azure Cloud Identity Providers

The Microsoft Azure cloud provides numerous authentication methods for cloud-hosted and “hybrid” on-premises applications. This includes options for either OpenID/OAuth or SAML authentication. Some of the identity solutions are Azure Active Directory (AAD), Azure B2C, Azure B2B, Azure Pass through authentication, Active Directory Federation Service (ADFS), migrate on-premises ADFS applications to Azure, Azure AD Connect with federation and SAML as IdP.

The following third-party identity providers implement the SAML 2.0 standard: Azure Active Directory (AAD), Okta, OneLogin, PingOne, and Shibboleth.

A Deep Dive Implementation

This blog post will walk through an example I recently worked on using federated authentication with the SAML protocol. I was able to dive deep into identity and authentication with an assigned proof of concept (POC) to create a claims-aware application within an ASP.NET Azure Web Application using the federated authentication and SAML protocol. I used OWIN middleware to connect to Identity Provider.

The scope of POC was not to develop an Identity Provider/STS (Security Token Service) but to develop a Service Provider/Relying Party (RP) which sends a SAML request and receives SAML tokens/assertions. The SAML tokens are used by the calling application to authorize the user into the application.

Given the scope, I used stub Identity Provider so that the authentication implementation could be plugged into a production application and communicate with other Enterprise SAML Identity Providers.

The Approach

For an application to be claims aware, it needs to obtain a claim token from an Identity Provider. The claim contained in the token is then used for additional authorization in the application. Claim tokens are issued by an Identity Provider after authenticating the user. The login page for the application (where the user signs in) can be a Service Provider (Relying Party) or just an ASP.NET UI application that communicates with the Service Provider via a separate implementation.

Figure 1: Overall architecture – Identity Provider Implementation

Figure 1: Overall architecture – Identity Provider Implementation

The Implementation

An ASP.NET MVC application was implemented as SAML Service provider with OWIN middleware to initiate the connection with the SAML Identity Provider.

First, the communication is initiated with a SAML request from service provider. The identity provider validates the SAML request, verifies and authenticates the user, and sends back the SAML tokens/assertions. The claims returned to service provider are then sent back to the client application. Finally, the client application can authorize the user after reviewing the claims returned from the SAML identity provider, based on roles or other more refined permissions.

SustainSys is an open-source solution and its SAML2 libraries add SAML2P support to ASP.NET web sites and serve as the SAML2 Service Provider (SP).  For the proof of concept effort, I used a stub SAML identity provider SustainSys Saml2 to test the SAML service provider. SustainSys also has sample implementations of a service provider from stub.

Implementation steps:

  • Start with an ASP.NET MVC application.
  • Add NuGet packages for OWIN middleware and SustainSys SAML2 libraries to the project (Figure 2).
  • Modify the Startup.cs (partial classes) to build the SAML request; set all authentication types such as cookies, default sign-in, and SAMLl2 (Listing 2).
  • In both methods CreateSaml2Options and CreateSPOptions SAML requests are built with both private and public certificates, federation SAML Identity Provider URL, etc.
  • The service provider establishes the connection to identity on start up and is ready to listen to client requests.
  • Cookie authentication is set, default authentication type is “Application,” and set the SAML authentication request by forming the SAML request.
  • When the SAML request options are set, instantiate Identity Provider with its URL and options. Set the Federation to true. Service Provider is instantiated with SAML request options with the SAML identity provider. Upon sign in by the user, OWIN middleware will issue a challenge to the Identity Provider and get the SAML response, claim/assertion back to the service provider.
  • OWIN Middleware issues a challenge to SAML Identity Provider with the callback method (ExternalLoginCallback(…)). Identity provider returns that callback method after authenticating the user (Listing 3).
  • AuthenticateSync will have claims returned from the Identity Provider and the user is authenticated at this point. The application can use the claims to authorize the user to the application.
  • No additional web configuration is needed for SAML Identity Provider communication, but the application config values can be persisted in web.config.

Figure 2: OWIN Middleware NuGet Packages

Figure 2: OWIN Middleware NuGet Packages

Listing 1:  Startup.cs (Partial)

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(Claims_MVC_SAML_OWIN_SustainSys.Startup))]

namespace Claims_MVC_SAML_OWIN_SustainSys
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

Listing 2: Startup.cs (Partial)

using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Sustainsys.Saml2;
using Sustainsys.Saml2.Configuration;
using Sustainsys.Saml2.Metadata;
using Sustainsys.Saml2.Owin;
using Sustainsys.Saml2.WebSso;
using System;
using System.Configuration;
using System.Globalization;
using System.IdentityModel.Metadata;
using System.Security.Cryptography.X509Certificates;
using System.Web.Hosting;

namespace Claims_MVC_SAML_OWIN_SustainSys
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {            
            // Enable Application Sign In Cookie
            var cookieOptions = new CookieAuthenticationOptions
                {
                    LoginPath = new PathString("/Account/Login"),
                AuthenticationType = "Application",
                AuthenticationMode = AuthenticationMode.Passive
            };

            app.UseCookieAuthentication(cookieOptions);

            app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);

            app.UseSaml2Authentication(CreateSaml2Options());
        }

        private static Saml2AuthenticationOptions CreateSaml2Options()
        {
            string samlIdpUrl = ConfigurationManager.AppSettings["SAML_IDP_URL"];
            string x509FileNamePath = ConfigurationManager.AppSettings["x509_File_Path"];

            var spOptions = CreateSPOptions();
            var Saml2Options = new Saml2AuthenticationOptions(false)
            {
                SPOptions = spOptions
            };

            var idp = new IdentityProvider(new EntityId(samlIdpUrl + "Metadata"), spOptions)
            {
                AllowUnsolicitedAuthnResponse = true,
                Binding = Saml2BindingType.HttpRedirect,
                SingleSignOnServiceUrl = new Uri(samlIdpUrl)
            };

            idp.SigningKeys.AddConfiguredKey(
                new X509Certificate2(HostingEnvironment.MapPath(x509FileNamePath)));

            Saml2Options.IdentityProviders.Add(idp);
            new Federation(samlIdpUrl + "Federation", true, Saml2Options);

            return Saml2Options;
        }

        private static SPOptions CreateSPOptions()
        {
            string entityID = ConfigurationManager.AppSettings["Entity_ID"];
            string serviceProviderReturnUrl = ConfigurationManager.AppSettings["ServiceProvider_Return_URL"];
            string pfxFilePath = ConfigurationManager.AppSettings["Private_Key_File_Path"];
            string samlIdpOrgName = ConfigurationManager.AppSettings["SAML_IDP_Org_Name"];
            string samlIdpOrgDisplayName = ConfigurationManager.AppSettings["SAML_IDP_Org_Display_Name"];

            var swedish = CultureInfo.GetCultureInfo("sv-se");
            var organization = new Organization();
            organization.Names.Add(new LocalizedName(samlIdpOrgName, swedish));
            organization.DisplayNames.Add(new LocalizedName(samlIdpOrgDisplayName, swedish));
            organization.Urls.Add(new LocalizedUri(new Uri("http://www.Sustainsys.se"), swedish));

            var spOptions = new SPOptions
            {
                EntityId = new EntityId(entityID),
                ReturnUrl = new Uri(serviceProviderReturnUrl),
                Organization = organization
            };
        
            var attributeConsumingService = new AttributeConsumingService("Saml2")
            {
                IsDefault = true,
            };

            attributeConsumingService.RequestedAttributes.Add(
                new RequestedAttribute("urn:someName")
                {
                    FriendlyName = "Some Name",
                    IsRequired = true,
                    NameFormat = RequestedAttribute.AttributeNameFormatUri
                });

            attributeConsumingService.RequestedAttributes.Add(
                new RequestedAttribute("Minimal"));

            spOptions.AttributeConsumingServices.Add(attributeConsumingService);

            spOptions.ServiceCertificates.Add(new X509Certificate2(
                AppDomain.CurrentDomain.SetupInformation.ApplicationBase + pfxFilePath));

            return spOptions;
        }
    }
}

Listing 3: AccountController.cs

using Claims_MVC_SAML_OWIN_SustainSys.Models;
using Microsoft.Owin.Security;
using System.Security.Claims;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace Claims_MVC_SAML_OWIN_SustainSys.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        public AccountController()
        {
        }

        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }

        //
        // POST: /Account/ExternalLogin
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult ExternalLogin(string provider, string returnUrl)
        {
            // Request a redirect to the external login provider
            return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
        }

        // GET: /Account/ExternalLoginCallback
        [AllowAnonymous]
        public ActionResult ExternalLoginCallback(string returnUrl)
        {
            var loginInfo = AuthenticationManager.AuthenticateAsync("Application").Result;
            if (loginInfo == null)
            {
                return RedirectToAction("/Login");
            }

            //Loop through to get claims for logged in user
            StringBuilder sb = new StringBuilder();
            foreach (Claim cl in loginInfo.Identity.Claims)
            {
                sb.AppendLine("Issuer: " + cl.Issuer);
                sb.AppendLine("Subject: " + cl.Subject.Name);
                sb.AppendLine("Type: " + cl.Type);
                sb.AppendLine("Value: " + cl.Value);
                sb.AppendLine();
            }
            ViewBag.CurrentUserClaims = sb.ToString();
            
            //ASP.NET ClaimsPrincipal is empty as Identity returned from AuthenticateAsync should be cast to IPrincipal
            //var identity = (ClaimsPrincipal)Thread.CurrentPrincipal;
            //var claims = identity.Claims;
            //string nameClaimValue = User.Identity.Name;
            //IEnumerable&amp;amp;lt;Claim&amp;amp;gt; claimss = ClaimsPrincipal.Current.Claims;
          
            return View("Login", new ExternalLoginConfirmationViewModel { Email = loginInfo.Identity.Name });
        }

        // Used for XSRF protection when adding external logins
        private const string XsrfKey = "XsrfId";

        private IAuthenticationManager AuthenticationManager
        {
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
        }
        internal class ChallengeResult : HttpUnauthorizedResult
        {
            public ChallengeResult(string provider, string redirectUri)
                : this(provider, redirectUri, null)
            {
            }

            public ChallengeResult(string provider, string redirectUri, string userId)
            {
                LoginProvider = provider;
                RedirectUri = redirectUri;
                UserId = userId;
            }

            public string LoginProvider { get; set; }
            public string RedirectUri { get; set; }
            public string UserId { get; set; }

            public override void ExecuteResult(ControllerContext context)
            {
                var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
                if (UserId != null)
                {
                    properties.Dictionary[XsrfKey] = UserId;
                }
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
        }
    }
}

Listing 4: Web.Config

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  https://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="SAML_IDP_URL" value="http://localhost:52071/" />
    <add key="x509_File_Path" value="~/App_Data/stubidp.sustainsys.com.cer"/>
    <add key="Private_Key_File_Path" value="/App_Data/Sustainsys.Saml2.Tests.pfx"/>
    <add key="Entity_ID" value="http://localhost:57234/Saml2"/>
    <add key="ServiceProvider_Return_URL" value="http://localhost:57234/Account/ExternalLoginCallback"/>
    <add key="SAML_IDP_Org_Name" value="Sustainsys"/>
    <add key="SAML_IDP_Org_Display_Name" value="Sustainsys AB"/>
  </appSettings>

Claims returned from the identity provider to service provider:

Claims returned from the identity provider to service provider

Additional References

Best of Texas AwardTwo years ago, the Texas Workforce Commission (TWC) came to AIS with an outdated online budgeting tool called the “Texas Reality Check” for middle and high school students. The application, designed to give students a clear sense of how much their desired future lifestyle will cost and what education and career choices will support it, was plagued by performance and accessibility issues…and its young target demographic was simply tuning it out.

AIS modernized the site for teen sensibilities, streamlined the underlying information architecture for easier use, overhauled the content strategy and user experience, and made it fully compliant with the latest accessibility guidelines.  You can read more about our work on this project here.

The new and improved Texas Reality Check has since gone on to become the most popular application of the Labor Market and Career Information Department (LMCI) of the Texas Workforce Commission. And now it’s been honored with a 2018 “Best of Texas” Award for Best Application Serving the Public. The awards highlight the Texas state government’s top creative tech implementations of the year, for both internal improvements and public-facing services like TRC.

“Governmental and educational leaders in Texas are leveraging technology to improve cybersecurity, enhance citizen service and advance emergency response, among many other things,” said Teri Takai, executive director of the Center for Digital Government. “Congratulations to this year’s Best of Texas winners for the vital role they are playing in advancing information technology in Texas.”

We’re really proud of our work on this project and thrilled that school students all across Texas have responded to the site in such a positive and engaged way. We hope the application continues to inspire them to dream big…while also equipping them with the knowledge and tools they need to achieve their goals.

I am pleased to announce my latest Pluralsight course on Power Apps (Well…such is the nature of change in the cloud that there has already been a name change since I submitted this course for publication, only a few weeks back. The aspect of Power Apps covered in my course is now referred to as Canvas Apps.)

This course is designed for developers (both citizen and professional developers) interested in a low-code approach for building mobile applications.

Here’s some background on Power Apps, if you haven’t had a chance to play with it yet:

Power Apps is a productive low-code development platform. It allows you to very quickly build business applications that can run inside a web browser, on a phone or a tablet. Power Apps includes a web-based IDE (Power Apps Studio, a set of built-in cross-platform controls), an Excel-like expression language that also includes imperative constructs like variables and loops, and over 130 connectors to talk to any number of data sources — including SQL Server, Office 365, Salesforce, Twitter, etc. You can also use custom connectors to talk to your domain-specific data source.

Beyond the controls, language expression and connectors, Power Apps provides ALM support in the form of app versioning, app publication to various app stores, swim-lanes for development environments, authentication and authorization (via Azure AD), RBAC controls, and security polices like data loss prevention (DLP).  All in all, the Power Apps service seeks to significantly lower the bar for building and distributing cross-platform mobile applications within your enterprise.

For a concrete example of our use of Power Apps, please read how we built a cross-platform event app in less than a week. Also please check out a recent episode of DotNetRocks where we talk about Power Apps.

Finally, as part of the latest spring update, Power Apps is combining with Dynamics 365 for Sales, Marketing, and Talent applications to offer an enterprise high-productivity application platform as a service (known as Microsoft Business Applications platform). What this means for Power Apps developers is that:

  1. They can now take advantage of server-side logic
  2. They have access to a data-centric way of building declarative apps, known as model-driven apps (in contrast to canvas apps, which are built by dragging and dropping controls to a canvas).

For more information on the spring update, please refer to this blog post by Frank Weigel.

I hope you will find this course useful. Please reach out to me via this blog or Twitter if you have any questions or comments.

Microsoft Power Apps and Flow have been generally available since late 2016. They’re both tools that allow business users to streamline business processes without the use of code. Microsoft positioned Power Apps as their recommended replacement for InfoPath as the business user’s forms designer, and Flow as their replacement for SharePoint Workflow.

While these are welcomed replacements, both solutions also provide a broader level of support to the Microsoft stack and across a wide array of third-party applications.  I’ve recently been working with Power Apps and Flow to replace some internal applications, as well as to build proof-of-concepts for our existing clients. Here’s what I think of each, both separately and when putting them together… Read More…

For the last few years, I have enjoyed participating in HOUR OF CODE – a global movement reaching tens of millions of students in 180+ countries. In 2017, 154,012 Hour Of Code events were registered worldwide.

To show how much fun (and useful) coding is, I wanted the kids to build something real,  vs. simply making their favorite character walk left or right.  I decided to use the MIT App Inventor tool for my Hour of Code sessions. App Inventor is a browser-based tool that allows you to build your own apps.  We built a simple Android app to help parents reduce distractions while driving. Even though the app is super simple, the results are cool enough for kids to proudly show the app to their parents.

Here is a 10-minute video of the steps we followed to build and test the app: Read More…

 

AIS recently completed work on a complete revamp of the Texas Workforce Commission’s “Texas Reality Check” website. Texas Reality Check is an Internet-available, fully accessible, responsive, mobile-first and browser-agnostic design. This website was tested for accessibility, performance, vulnerability scans, and usability.

Background

Texas Reality Check (TRC) is targeted at students on a statewide basis, ranging from middle school to high school (with some colleges and universities making use of the tool for “life skills” classes). The goal is to inspire students to think about occupations, and prepare for educational requirements so they can achieve the income level that meets their lifestyle expectations.

This tool walks students through different areas of life, on a step-by step-basis, identifying budgets associated with living essentials such as housing, transportation, food, clothing, etc. Students make selections and then calculate a corresponding monthly income that would afford the selections they make. From here, the students are directed to another page and connected to a database on careers and associated salaries.

However, the existing site was dated and in need of improvements in three core areas: UX, Accessibility, and overall performance. Here’s how AIS delivered:

Read More…

Microsoft announced a significant and dramatic convergence of its Windows 8.1 and Windows Phone 8.1 platforms during the Build 2014 conference.  Approximately 90% of the WinRT APIs are now converged between the two platforms. Microsoft Visual Studio 2013 Update 2 includes features that enable developers to take advantage of this platform convergence. The Universal Apps project template is one of those features.  Read More…
Answers.com needed a Microsoft partner that could upgrade their Windows 8 application to maximize the use of a variety of new 8.1 features. Expediency was of the utmost importance in this instance and based on our prior experience with Microsoft, they knew we were the best partner for the job. AIS upgraded the Answers.com Windows 8.0 Store application to 8.1, meeting Answers.com goal of getting it done as quickly as possible. Our team also validated the application through the Microsoft DPE Windows 8 Partner Program to ensure adherence to all Windows 8.1 criterion. Read more…