{"id":77521,"date":"2018-03-09T17:21:55","date_gmt":"2018-03-09T17:21:55","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=77521"},"modified":"2022-05-24T20:05:12","modified_gmt":"2022-05-24T20:05:12","slug":"securing-angular-based-chrome-extensions-using-azure-ad-asp-net-core","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/javascript\/securing-angular-based-chrome-extensions-using-azure-ad-asp-net-core\/","title":{"rendered":"Azure AD Authentication in Angular Chrome Extensions via ASP.NET Core Proxy"},"content":{"rendered":"<p>The Azure AD <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/active-directory\/develop\/active-directory-code-samples\">reference documentation<\/a> delivers tons of examples describing scenarios in which this authorization service can be used. Those examples help to find the answer for the very first question a developer asks when starting to research Azure AD authentication: which kind of solution should I choose for my project? The use case examples provided there are really helpful if you find one of them matching your project requirements.<\/p>\n<p>Unfortunately, Chrome extensions do not seem to be on a list of typical scenarios. The extensions are simple web applications built using HTML and JavaScript. Microsoft provides a ADAL.JS library that allows you to authorize web applications using the OAuth protocol. It simply redirects the user to the login page on Azure. After successful authorization the user is redirected back to the web application. That might seem to be a perfect solution for a Chrome extension. However, there is one technical issue that rules out this approach: Chrome extensions do not use the <em>http<\/em>\/<em>https<\/em> protocol! They use the <em>chrome-extension:\/\/<\/em> protocol instead. All attempts of setting up the ADAL.JS configuration using redirect page that doesn\u2019t use <em>http<\/em> or <em>https<\/em> protocol result with an error like that shown in Figure 1.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"467\" height=\"398\" class=\"wp-image-77522\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-25.png\" \/><\/p>\n<p class=\"caption\">Figure 1: The login error<\/p>\n<h2>Find a Way Out<\/h2>\n<p>Let\u2019s think about a way out. What if we delegated the authorization process to a web application hosted using standard the http protocol? Its role would be to obtain an authorization token from Azure AD and pass it to the Chrome extension. The diagram shown in Figure 2 presents the flow of a proposed solution. The <a href=\"https:\/\/github.com\/jakubkaczmarek\/ng-chrome-extension-with-azure-ad\">code with a working solution<\/a> covered in this article can be found on my GitHub.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1348\" height=\"706\" class=\"wp-image-77523\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-26.png\" \/><\/p>\n<p class=\"caption\">Figure 2: Proposed solution workflow<\/p>\n<h2>Getting Started<\/h2>\n<p>My previous <a href=\"https:\/\/www.red-gate.com\/simple-talk\/dotnet\/software-tools\/developing-google-chrome-extension-using-angular-4\/\">article<\/a> presented how to build Chrome extensions using TypeScript and Angular CLI. I will use the code developed in that article to demonstrate how to implement Azure AD authentication in this case. The goal is to restrict some content for authorized users only.<\/p>\n<p>Before implementing the authorization solution, register a new application registration in Azure Active Directory module on the Azure portal. More details can be found in the Azure Active Directory documentation. While registering it, you need to provide some details. Here are the settings to use for the test application:<\/p>\n<ul>\n<li>Display name: ChromeTestApp<\/li>\n<li>Application type: Web app \/ API<\/li>\n<li>Home page URL: h<code>ttp:\/\/localhost:9100\/home\/signout<\/code><\/li>\n<li>Logout URL: <code>http:\/\/localhost:9100\/home\/signout<\/code><\/li>\n<li>Reply URLs (add new position): <code>http:\/\/localhost:9100\/home\/signin<\/code><\/li>\n<\/ul>\n<p>After creating the app registration, copy <em>Tenant ID<\/em> and <em>Application ID<\/em>. <em>Application ID<\/em> can be found in the <em>Essentials<\/em> blade. <em>Tenant ID<\/em> is a directory name of your organization on Azure. It&#8217;s visible in <em>All settings\/Properties\/App ID URI<\/em> field as a part of the url generated by default for the registration, according to the following format:<\/p>\n<p><code>https:\/\/<strong>organization-name.onmicrosoft.com<\/strong>\/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx<\/code><\/p>\n<p>The bolded part of the URL above is your Tenant ID and highlighted in Figure 3.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1003\" height=\"271\" class=\"wp-image-77524\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-27.png\" \/><\/p>\n<p class=\"caption\">Figure 3: Setting up the ChromeTestApp properties<\/p>\n<h2>Building the Authorization Application<\/h2>\n<p>Before making changes in the extension code, you should take care of the middle of the flow \u2013 the authorization proxy app. This web application can be developed using any kind of programming language and framework. The only condition is that it should be capable of hosting web pages that can load JavaScript files. This article uses the ASP.NET Core framework.<\/p>\n<p>Firstly, create a new ASP.NET Core web application. Name it <em>AuthorizationApp <\/em>as shown in Figure 4.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"941\" height=\"653\" class=\"wp-image-77525\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-28.png\" \/><\/p>\n<p class=\"caption\">Figure 4: Create an ASP.NET Core Web-Application<\/p>\n<p>Use the <em>Empty<\/em> template to create it shown in Figure 5.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"786\" height=\"513\" class=\"wp-image-77526\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-29.png\" \/><\/p>\n<p class=\"caption\">Figure 5: Use the Empty template<\/p>\n<p>The application should host two pages \u2013 one which handles the login action, and the second for logout. Begin by creating the app configuration file (<em>appsettings.json<\/em>) by adding a new item of type <em>ASP.NET Configuration File<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">{\r\n    \"AppSettings\": {\r\n      \"TenantId\": \"organization-name.onmicrosoft.com\",\r\n      \"ClientId\": \"00000000-0000-0000-0000-000000000000\"\r\n    }\r\n  }<\/pre>\n<p>You will also need the <em>AppSettings<\/em> model class (<em>Models\/AppSettings.cs<\/em>) covering settings included in the configuration file:<\/p>\n<pre class=\"lang:c# theme:vs2012\">namespace AuthorizationApp.Models\r\n  {\r\n      public class AppSettings\r\n      {\r\n          public string TenantId { get; set; }\r\n          public string ClientId { get; set; }\r\n      }\r\n  }<\/pre>\n<p>One of the cool new features in ASP.NET Core is the possibility to map a configuration file to a model class. Combined with another feature \u2013 built-in dependency injection container \u2013 you can make the <em>AppSettings<\/em> class injectable to any other class that will be resolved using DI. To use this feature, first install additional NuGet packages to the project:<\/p>\n<ul>\n<li>Microsoft.Extensions.Configuration<\/li>\n<li>Microsoft.Extensions.Configuration.Json<\/li>\n<\/ul>\n<p>Now it\u2019s time to enhance the Startup code (<em>startup.cs<\/em>):<\/p>\n<pre class=\"lang:c# theme:vs2012 \">  using AuthorizationApp.Models;\r\n  using Microsoft.AspNetCore.Builder;\r\n  using Microsoft.AspNetCore.Hosting;\r\n  using Microsoft.Extensions.Configuration;\r\n  using Microsoft.Extensions.DependencyInjection;\r\n  namespace AuthorizationApp\r\n  {\r\n      public class Startup\r\n      {\r\n          public Startup(IConfiguration configuration)\r\n          {\r\n              Configuration = configuration;\r\n          }\r\n          public IConfiguration Configuration { get; }\r\n          public void ConfigureServices(IServiceCollection services)\r\n          {\r\n              services.Configure&lt;AppSettings&gt;(Configuration.GetSection(\"AppSettings\"));\r\n              services.AddMvc();\r\n          }\r\n          public void Configure(IApplicationBuilder app, IHostingEnvironment env)\r\n          {\r\n              if (env.IsDevelopment())\r\n              {\r\n                  app.UseDeveloperExceptionPage();\r\n              }\r\n              app.UseStaticFiles();\r\n              app.UseMvc(routes =&gt;\r\n              {\r\n                  routes.MapRoute(\r\n                      name: \"default\",\r\n                      template: \"{controller}\/{action=Index}\/{id?}\");\r\n              });\r\n          }\r\n      }\r\n  }<\/pre>\n<h2>Authorization Controller<\/h2>\n<p>As you will need to provide some configuration entries for ADAL.JS, create an <em>AuthorizationModel<\/em> class (<em>Models\/AuthorizationModel.cs<\/em>) that can be passed to the view of the pages:<\/p>\n<pre class=\"lang:c# theme:vs2012\">namespace AuthorizationApp.Models\r\n  {\r\n      public class AuthorizationModel\r\n      {\r\n          public string TenantId { get; set; }\r\n          public string ClientId { get; set; }\r\n          public string ScriptBundleName { get; set; }\r\n      }\r\n  }<\/pre>\n<p>Now you\u2019re ready to implement the <em>HomeController <\/em>class (<em>Controllers\/HomeController.cs<\/em>):<\/p>\n<pre class=\"lang:c# theme:vs2012 \">  using AuthorizationApp.Models;\r\n  using Microsoft.AspNetCore.Mvc;\r\n  using Microsoft.Extensions.Options;\r\n  namespace AuthorizationApp.Controllers\r\n  {\r\n      public class HomeController : Controller\r\n      {\r\n          private readonly AppSettings _appSettings;\r\n          public HomeController(IOptions&lt;AppSettings&gt; appSettings)\r\n          {\r\n              _appSettings = appSettings.Value;\r\n          }\r\n          public ActionResult SignIn()\r\n          {\r\n              return GetView(\"signin.js\");\r\n          }\r\n          public ActionResult SignOut()\r\n          {\r\n              return GetView(\"signout.js\");\r\n          }\r\n          private ViewResult GetView(string scriptBundleName)\r\n          {\r\n              var model = new AuthorizationModel\r\n              {\r\n                  ClientId = _appSettings.ClientId,\r\n                  TenantId = _appSettings.TenantId,\r\n                  ScriptBundleName = scriptBundleName\r\n              };\r\n              return View(\"SignInOut\", model);\r\n          }\r\n      }\r\n  }\r\n<\/pre>\n<p>New actions share the same view \u2013 <em>SignInOut.cshtml<\/em>. The reason for this is that both pages have a very similar objective \u2013 display no content, just initialize some configurations and include a single JavaScript file.<\/p>\n<h2>Implementing the View<\/h2>\n<p>Before you can implement the view file, make sure the following JavaScript files are included in <em>wwwroot\/js<\/em> directory:<\/p>\n<ul>\n<li><em>jQuery <\/em>library (<a href=\"https:\/\/jquery.com\/download\">https:\/\/jquery.com\/download<\/a>)<\/li>\n<li><em>ADAL.JS <\/em>library (<a href=\"https:\/\/github.com\/AzureAD\/azure-activedirectory-library-for-js\">https:\/\/github.com\/AzureAD\/azure-activedirectory-library-for-js<\/a>)<\/li>\n<li>New custom script files (empty):\n<ul>\n<li><em>signin.js<\/em><\/li>\n<li><em>signout.js<\/em><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>The application will have only one view file, defined in the <em>Views\/Home<\/em> directory (<em>Views\/Home\/SignInOut.cshtml<\/em>):<\/p>\n<pre class=\"lang:c# theme:vs2012\">@model AuthorizationApp.Models.AuthorizationModel\r\n  &lt;!DOCTYPE html&gt;\r\n  &lt;html&gt;\r\n      &lt;head&gt;\r\n          &lt;meta charset=\"utf-8\" \/&gt;\r\n          &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/&gt;\r\n          &lt;script&gt;\r\n          window.adalConfig = {\r\n              instance: 'https:\/\/login.microsoftonline.com\/',\r\n              tenantId: '@Model.TenantId',\r\n              clientId: '@Model.ClientId',\r\n              postLogoutRedirectUri: window.location.origin + '\/home\/signout',\r\n              cacheLocation: 'localStorage'\r\n          };\r\n          &lt;\/script&gt;\r\n          &lt;script src=\"~\/js\/jquery.min.js\"&gt;&lt;\/script&gt;\r\n          &lt;script src=\"~\/js\/adal.min.js\"&gt;&lt;\/script&gt;\r\n          &lt;script src=\"~\/js\/@Model.ScriptBundleName\"&gt;&lt;\/script&gt;\r\n          &lt;title&gt;Authorization App&lt;\/title&gt;\r\n      &lt;\/head&gt;\r\n      &lt;body&gt;\r\n      &lt;\/body&gt;\r\n  &lt;\/html&gt;<\/pre>\n<h2>Use the ADAL.JS Library<\/h2>\n<p>You\u2019re finally ready to use the ADAL.JS library. At first, you can develop the <em>signin.js<\/em> code (<em>wwwroot\/js\/signin.js)<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\"><a id=\"post-77521-30j0zll\"><\/a><a id=\"post-77521-1fob9te\"><\/a>(function () {\r\n      var adalConfig = window.adalConfig;\r\n      var authContext = new AuthenticationContext(adalConfig);\r\n      var isCallback = authContext.isCallback(window.location.hash);\r\n      authContext.handleWindowCallback();\r\n      if (isCallback &amp;&amp; !authContext.getLoginError()) {\r\n          window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);\r\n      }\r\n      if (!authContext.getLoginError() &amp;&amp; authContext.getCachedUser() != null) {\r\n          localStorage.setItem('loggedIn', 'true');\r\n      }\r\n      window.onhashchange = function () {\r\n          loadView(stripHash(window.location.hash));\r\n      };\r\n      window.onload = function () {\r\n          $(window).trigger(\"hashchange\");\r\n      };\r\n      function loadView(view) {\r\n          if (!authContext.getCachedUser()) {\r\n              authContext.config.redirectUri = window.location.href;\r\n              authContext.login();\r\n              return;\r\n          }\r\n      }\r\n      function stripHash(view) {\r\n          return view.substr(view.indexOf('#') + 1);\r\n      }\r\n  <a id=\"post-77521-_3znysh7\"><\/a>}());\r\n<\/pre>\n<p>The Sign out script is even simpler (<em>wwwroot\/js\/signout.js):<\/em><\/p>\n<pre class=\"lang:c# theme:vs2012\"><a id=\"post-77521-2et92p0\"><\/a><a id=\"post-77521-tyjcwt\"><\/a>(function () {\r\n      var adalConfig = window.adalConfig;\r\n      var authContext = new AuthenticationContext(adalConfig);\r\n      var user = authContext.getCachedUser();\r\n      if (user) {\r\n          authContext.logOut();\r\n      }\r\n      else {\r\n          localStorage.setItem('loggedIn', 'false');\r\n      }\r\n  }());\r\n<\/pre>\n<p>That\u2019s it \u2013 the web application that will handle Azure AD authorization is ready. Before running it locally, please make sure its URL has a port number matching your Azure AD App Registration. In my case it is port <em>9100<\/em>. You can configure it in Visual Studio in Project Properties as shown in Figure 6:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"857\" height=\"653\" class=\"wp-image-77527\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-30.png\" \/><\/p>\n<p class=\"caption\">Figure 6: Configuring the App URL<\/p>\n<p>The app can be hosted locally using IIS Express. For production use, it can be deployed in the cloud, e.g. as Azure App Service.<\/p>\n<p>As you can see, the connection between the app and the extension is not implemented on the authorization app side. That\u2019s the part that will be implemented on the extension side.<\/p>\n<h2>Enhancing the Extension: Content Script<\/h2>\n<p>As the proposed solution assumes that the extension implements the event page and content script, you need at first to make sure that the extension on has those elements.<\/p>\n<p>Chrome extensions can include a content script that runs in the context of web page loaded in the browser tab. That\u2019s the perfect place to implement logic that will communicate with the authorization web app, collect the authorization token, and use it to authorize the extension.<\/p>\n<p>In this example, the <em>ADAL.JS<\/em> library uses browser local storage to keep authorization details, including the token. A content script running in the context of authorized web application has access to the authorization details, as it can browse the local storage of the authorization app. However, a content script doesn\u2019t have access to the extension\u2019s local storage. On the other hand, the event page script has access to it. You can use the messaging API to send messages from content scripts to event page scripts. You will need to send two such messages:<\/p>\n<ul>\n<li>After successful login action\n<ul>\n<li>Message should include ADAL authorization data<\/li>\n<\/ul>\n<\/li>\n<li>After successful logout action.<\/li>\n<\/ul>\n<p>All new functionality will make use of ADAL.JS and Chrome extensions APIs &#8211; functionalities that are not known for TypeScript. However, you can install additional packages to fix that issue:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">  npm install \u2013s adal-ts\r\n  npm install \u2013s @types\/chrome<\/pre>\n<p>You will also need a new configuration value stored in the environment collection. It\u2019s necessary to include it in all instances of the environment class (<em>src\/environments\/environment.ts)<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">export const environment = {\r\n      production: false,\r\n      authAppBaseUrl: 'http:\/\/localhost:9100'\r\n  };<\/pre>\n<p>It must also be included here, for example (<em>src\/environments\/environment.prod.ts):<\/em><\/p>\n<pre class=\"lang:c# theme:vs2012\">export const environment = {\r\n      production: true,\r\n      authAppBaseUrl: 'http:\/\/localhost:9100'\r\n  };<\/pre>\n<p>Some of the new code can be shared between content script and event page. First implement the Angular service that will be common for both (<em>src\/app\/common\/services\/common-account.service.ts)<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">  import { environment } from '..\/..\/..\/environments\/environment';\r\n  import { Authentication, AuthenticationContext, AdalConfig, User } from 'adal-ts';\r\n  export class CommonAccountService {\r\n      private baseUrl = environment.authAppBaseUrl;\r\n      private loggedInKey = 'loggedIn';\r\n      public userExistsAndIsNotExpired(): boolean {\r\n          const user = this.getAuthorizedUser();\r\n          if (!user) {\r\n              return false;\r\n          }\r\n          const isTokenExpired = this.isAuthorizationTokenExpired(user.exp);\r\n          return !isTokenExpired;\r\n      }\r\n      public isUserLoggedIn(): boolean {\r\n          const loggedInValue = localStorage.getItem(this.loggedInKey);\r\n          const loggedIn = loggedInValue === 'true';\r\n          return loggedIn;\r\n      }\r\n      public getUserName(): string {\r\n          const user = this.getAuthorizedUser();\r\n          let result = '';\r\n          if (user) {\r\n              result = user.name;\r\n          }\r\n          return result;\r\n      }\r\n      public redirectToLoginPage() {\r\n          this.openUrlInCurrentTab(this.getLoginUrl());\r\n      }\r\n      public redirectToLogoutPage() {\r\n          this.openUrlInCurrentTab(this.getLogoutUrl());\r\n      }\r\n      public getLoginUrl(): string {\r\n          return this.getAuthorizationAppUrl('home\/signin');\r\n      }\r\n      public getLogoutUrl(): string {\r\n          return this.getAuthorizationAppUrl('home\/signout');\r\n      }\r\n      public isAccountKey(key: string): boolean {\r\n          return key.indexOf('adal.') &gt;= 0 || key === this.loggedInKey;\r\n      }\r\n      getAuthorizedUser(): User {\r\n          return this.getAuthenticationContext().getUser();\r\n      }\r\n      getAuthorizationAppUrl(relativePath: string) {\r\n          if (relativePath[0] !== '\/') {\r\n              relativePath = '\/' + relativePath;\r\n          }\r\n          return this.baseUrl + relativePath;\r\n      }\r\n      openUrlInCurrentTab(targetUrl) {\r\n          chrome.tabs.query({ active: true, currentWindow: true }, (tabs) =&gt; {\r\n              chrome.tabs.update(tabs[0].id, { url: targetUrl });\r\n          });\r\n      }\r\n      getAuthenticationContext(): AuthenticationContext {\r\n          return Authentication.getContext(new AdalConfig('', '', ''));\r\n      }\r\n      isAuthorizationTokenExpired(expirationTimestamp: number): boolean {\r\n          if (!expirationTimestamp) {\r\n              return true;\r\n          }\r\n          const expirationDate = new Date(expirationTimestamp * 1000);\r\n          const currentDate = new Date();\r\n          return currentDate &gt; expirationDate;\r\n      }\r\n  }<\/pre>\n<p><em><br \/>\n<\/em> As the content script consists of other modules, you can implement an additional one in a separate file (<em>content-script\/account-<\/em>m<em>essenger.ts)<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">  import { CommonAccountService } from '..\/src\/app\/common\/services\/common-account.service';\r\n  export class AccountMessenger {\r\n      private commonAccountService: CommonAccountService;\r\n      constructor() {\r\n          this.commonAccountService = new CommonAccountService();\r\n          this.initializeSigninListener();\r\n          this.initializeSignoutListener();\r\n      }\r\n      initializeSigninListener() {\r\n          const loginUrl = this.commonAccountService.getLoginUrl();\r\n          if (window.location.href.indexOf(loginUrl) &lt; 0) {\r\n              return;\r\n          }\r\n          const loggedIn = this.commonAccountService.isUserLoggedIn();\r\n          if (!loggedIn) {\r\n              return;\r\n          }\r\n          const adalData = this.getAdalLocalStorageData();\r\n          const message = {\r\n              'command': 'signInAndRedirectToHomepage',\r\n              'adalData': adalData\r\n          };\r\n          this.performEventPageRequest(message);\r\n      }\r\n      initializeSignoutListener() {\r\n          const logoutUrl = this.commonAccountService.getLogoutUrl();\r\n          if (window.location.href.indexOf(logoutUrl) &lt; 0) {\r\n              return;\r\n          }\r\n          const loggedIn = this.commonAccountService.isUserLoggedIn();\r\n          if (loggedIn) {\r\n              return;\r\n          }\r\n          const message = {\r\n              'command': 'signOutAndRedirectToHomepage'\r\n          };\r\n          this.performEventPageRequest(message);\r\n      }\r\n      getAdalLocalStorageData() {\r\n          const result = {};\r\n          for (const key in localStorage) {\r\n              if (this.commonAccountService.isAccountKey(key)) {\r\n                  result[key] = localStorage[key];\r\n              }\r\n          }\r\n          return result;\r\n      }\r\n      performEventPageRequest(message) {\r\n          chrome.runtime.sendMessage(message);\r\n      }\r\n  }<\/pre>\n<p><em><br \/>\n<\/em> In my previous article, a content script code was introduced. The implementation was based on the <em>boot.ts<\/em> script in the <em>content-script<\/em> folder responsible for launching some classes with content script logic (<em>RuntimeListener<\/em> and <em>ConnectListener<\/em>). Since you inherited this solution, you can enhance it now to launch also <em>AccountMessenger<\/em> logic (<em>content-script\/boot.ts)<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\"><a id=\"post-77521-3dy6vkm\"><\/a><a id=\"post-77521-1t3h5sf\"><\/a>import { AccountMessenger } from '.\/account-messenger';\r\n  import { ConnectListener } from '.\/connect-listener';\r\n  import { RuntimeListener } from '.\/runtime-listener';\r\n  const runtimeListener = new RuntimeListener();\r\n  const connectListener = new ConnectListener();\r\n  const accountMessenger = new AccountMessenger();\r\n<\/pre>\n<p>Finally, a provider of <em>CommonAccountService<\/em> needs to be registered in an Angular application as you&#8217;re about to inject it also into some components of the app. A reference to the service should be included in the A<em>ppModule<\/em> class (<em>src\/app\/app.module.ts)<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\"><a id=\"post-77521-4d34og8\"><\/a> import { BrowserModule } from '@angular\/platform-browser';\r\n  import { NgModule } from '@angular\/core';\r\n  import { AppComponent } from '.\/app.component';\r\n  import { HomepageComponent } from '.\/homepage\/homepage.component';\r\n  import { EventPageComponent } from '.\/event-page\/event-page.component';\r\n  import { PopupComponent } from '.\/popup\/popup.component';\r\n  import { AppRoutingModule } from '.\/app-routing.module';\r\n  import { CommonAccountService } from '.\/common\/services\/common-account.service';\r\n  @NgModule({\r\n      declarations: [\r\n          AppComponent,\r\n          HomepageComponent,\r\n          EventPageComponent,\r\n          PopupComponent\r\n      ],\r\n      imports: [\r\n          AppRoutingModule,\r\n          BrowserModule\r\n      ],\r\n      providers: [CommonAccountService],\r\n      bootstrap: [AppComponent]\r\n  })\r\n  export class AppModule { }<\/pre>\n<h2>Enhancing the Extension: Event Page Script<\/h2>\n<p>The last missing part of the extension is the authorization messages receiver. It should be able to receive two command messages from the content script:<\/p>\n<ul>\n<li><em>signInAndRedirectToHomepage<\/em>:\n<ul>\n<li>On this action store <em>ADAL<\/em> configuration values in extension local storage.<\/li>\n<\/ul>\n<\/li>\n<li><em>signOutAndRedirectToHomepage<\/em>:\n<ul>\n<li>On this action clear ADAL configuration values from extension local storage.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>After each action, the user should be redirected to the homepage. Implement this logic in the <em>EventPageComponent<\/em> class (<em>src\/app\/event-page\/event-page.component.ts)<\/em>.<\/p>\n<pre class=\"lang:c# theme:vs2012\">import { Component, OnInit } from '@angular\/core';\r\nimport { CommonAccountService } from '..\/common\/services\/common-account.service';\r\n  @Component({\r\n      selector: 'app-event-page',\r\n      templateUrl: '.\/event-page.component.html',\r\n      styleUrls: ['.\/event-page.component.css']\r\n  })\r\n  export class EventPageComponent implements OnInit {\r\n      constructor(private accountService: CommonAccountService) { }\r\n      ngOnInit() {\r\n          this.addMessageListener();\r\n      }\r\n      addMessageListener() {\r\n          chrome.runtime.onMessage.addListener((request, sender, sendResponse) =&gt; {\r\n              switch (request.command) {\r\n              case 'signInAndRedirectToHomepage': this.signInAndRedirectToHomepage(request.adalData);\r\n                  break;\r\n              case 'signOutAndRedirectToHomepage': this.signOutAndRedirectToHomepage();\r\n                  break;\r\n              default: return;\r\n              }\r\n          });\r\n      }\r\n      signInAndRedirectToHomepage(adalData) {\r\n          this.updateAdalData(adalData);\r\n          this.redirectToHomepage();\r\n      }\r\n      signOutAndRedirectToHomepage() {\r\n          this.clearAdalData();\r\n          this.redirectToHomepage();\r\n      }\r\n      redirectToHomepage() {\r\n          chrome.tabs.query({ active: true, currentWindow: true }, (tabs) =&gt; {\r\n              const tab = tabs[0];\r\n              const homepageUrl = chrome.extension.getURL('index.html#\/homepage');\r\n              chrome.tabs.update(tab.id, { url: homepageUrl });\r\n          });\r\n      }\r\n      updateAdalData(adalData) {\r\n          for (const key in adalData) {\r\n              const value = adalData[key];\r\n              localStorage.setItem(key, value);\r\n          }\r\n      }\r\n      clearAdalData() {\r\n          for (const key in localStorage) {\r\n              if (this.accountService.isAccountKey(key)) {\r\n                  localStorage.removeItem(key);\r\n              }\r\n          }\r\n      }\r\n  }\r\n<\/pre>\n<p>Note: the decorators of the <em>EventPageComponent<\/em> class were generated automatically by the Angular CLI generate component command. If your project wasn\u2019t created with Angular CLI, you might notice a warning message during the build about questionable support of experimental decorators. To get rid of this warning, you should enable the <em>experimentalDecorators<\/em> option in the <em>tsconfig.json<\/em> file. If your project doesn\u2019t have such a file, you should create it in angular app root folder with the following content (<em>tsconfig.json):<\/em><\/p>\n<pre class=\"lang:c# theme:vs2012\">{\r\n    \"compilerOptions\": {\r\n      \"experimentalDecorators\": true,\r\n      \"allowJs\": true\r\n    }\r\n  }\r\n\r\n<\/pre>\n<h2>Secured Content<\/h2>\n<p>It\u2019s time to consume the authorization capability in the extension. Define a place, where the content needs to be visible only for an authorized user. Do this in the homepage component. At first, you will need the piece of html code that renders the login\/logout buttons (<em>src\/app\/homepage\/homepage.component.html)<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">&lt;h1&gt;Homepage&lt;\/h1&gt;\r\n  &lt;div class=\"user-status\"&gt;\r\n      &lt;div class=\"welcome\"&gt;\r\n          &lt;p *ngIf=\"loggedIn\"&gt;Welcome, <strong>{{<\/strong>userName<strong>}}<\/strong>!&lt;\/p&gt;\r\n          &lt;p *ngIf=\"!loggedIn\"&gt;Please log in!&lt;\/p&gt;\r\n      &lt;\/div&gt;\r\n      &lt;div class=\"buttons\"&gt;\r\n          &lt;button *ngIf=\"loggedIn\" (click)=\"logout()\"&gt;Log out&lt;\/button&gt;\r\n          &lt;button *ngIf=\"!loggedIn\" (click)=\"login()\"&gt;Log in&lt;\/button&gt;\r\n      &lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n  &lt;div *ngIf=\"loggedIn\" class=\"content-container\"&gt;\r\n      &lt;img src='assets\/secret.png' \/&gt;\r\n  &lt;\/div&gt;<\/pre>\n<p><em>\u00a0<\/em><\/p>\n<p>The last thing to do is to include the <em>common.account.service<\/em> module in <em>homepage.component<\/em> class and implement some logic there (<em>src\/app\/homepage\/homepage.component.ts):<\/em><\/p>\n<pre class=\"lang:c# theme:vs2012 \">  import { CommonAccountService } from '..\/common\/services\/common-account.service';\r\n  import { Component, OnInit } from '@angular\/core';\r\n  @Component({\r\n      selector: 'app-homepage',\r\n      templateUrl: '.\/homepage.component.html',\r\n      styleUrls: ['.\/homepage.component.css']\r\n  })\r\n  export class HomepageComponent implements OnInit {\r\n      userName: string;\r\n      loggedIn: boolean;\r\n      constructor(private accountService: CommonAccountService) {\r\n          this.loggedIn = accountService.userExistsAndIsNotExpired();\r\n          if (this.loggedIn) {\r\n              this.userName = accountService.getUserName();\r\n          }\r\n      }\r\n      ngOnInit() { }\r\n      login() {\r\n          this.accountService.redirectToLoginPage();\r\n      }\r\n      logout() {\r\n          this.accountService.redirectToLogoutPage();\r\n      }\r\n  }<\/pre>\n<p><em><br \/>\n<\/em> Starting from now, the Chrome extension requires authorization of the user simply to show a content of <em>content-container<\/em> div element. You can enhance this logic to condition access to other interface areas, data-layer or whatever else is needed. After building the app using <em>gulp<\/em> command and loading it as a chrome extension, you can see the following view on extension homepage shown in Figure 7:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"144\" height=\"108\" class=\"wp-image-77528\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-31.png\" \/><\/p>\n<p class=\"caption\">Figure 7: The login page<\/p>\n<p>After logging in, you can see the content available only for authorized users (Figure 8):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"289\" height=\"370\" class=\"wp-image-77529\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/03\/word-image-32.png\" \/><\/p>\n<p class=\"caption\">Figure 8: Content shown to authorized users<\/p>\n<h2>Summary<\/h2>\n<p>A powerful Chrome Extensions API allowed us to deal with Azure AD limits regarding URLs that are accepted as reply URL for App Registration. By introducing a middle layer web application responsible for obtaining &amp; sharing the authorization token the initial problem is solved. In addition to that, I also introduced the web API that might be used in future as back-end part of the extension.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Note: Uses ADAL.js (deprecated 2023 &#8211; see MSAL.js for current implementations). Covers securing an Angular Chrome extension with Azure AD using an ASP.NET Core proxy app to work around Chrome extension URL restrictions.&hellip;<\/p>\n","protected":false},"author":316282,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[146992,146044],"tags":[95509],"coauthors":[50410],"class_list":["post-77521","post","type-post","status-publish","format-standard","hentry","category-angular","category-javascript","tag-standardize"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/77521","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/316282"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=77521"}],"version-history":[{"count":13,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/77521\/revisions"}],"predecessor-version":[{"id":94475,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/77521\/revisions\/94475"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=77521"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=77521"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=77521"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=77521"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}