Securing Angular-Based Chrome Extensions Using Azure AD and ASP.NET CORE

Comments 0

Share to social media

The Azure AD reference documentation 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.

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 http/https protocol! They use the chrome-extension:// protocol instead. All attempts of setting up the ADAL.JS configuration using redirect page that doesn’t use http or https protocol result with an error like that shown in Figure 1.

Figure 1: The login error

Find a Way Out

Let’s 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 code with a working solution covered in this article can be found on my GitHub.

Figure 2: Proposed solution workflow

Getting Started

My previous article 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.

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:

  • Display name: ChromeTestApp
  • Application type: Web app / API
  • Home page URL: http://localhost:9100/home/signout
  • Logout URL: http://localhost:9100/home/signout
  • Reply URLs (add new position): http://localhost:9100/home/signin

After creating the app registration, copy Tenant ID and Application ID. Application ID can be found in the Essentials blade. Tenant ID is a directory name of your organization on Azure. It’s visible in All settings/Properties/App ID URI field as a part of the url generated by default for the registration, according to the following format:

https://organization-name.onmicrosoft.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

The bolded part of the URL above is your Tenant ID and highlighted in Figure 3.

Figure 3: Setting up the ChromeTestApp properties

Building the Authorization Application

Before making changes in the extension code, you should take care of the middle of the flow – 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.

Firstly, create a new ASP.NET Core web application. Name it AuthorizationApp as shown in Figure 4.

Figure 4: Create an ASP.NET Core Web-Application

Use the Empty template to create it shown in Figure 5.

Figure 5: Use the Empty template

The application should host two pages – one which handles the login action, and the second for logout. Begin by creating the app configuration file (appsettings.json) by adding a new item of type ASP.NET Configuration File:

You will also need the AppSettings model class (Models/AppSettings.cs) covering settings included in the configuration file:

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 – built-in dependency injection container – you can make the AppSettings class injectable to any other class that will be resolved using DI. To use this feature, first install additional NuGet packages to the project:

  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.Json

Now it’s time to enhance the Startup code (startup.cs):

Authorization Controller

As you will need to provide some configuration entries for ADAL.JS, create an AuthorizationModel class (Models/AuthorizationModel.cs) that can be passed to the view of the pages:

Now you’re ready to implement the HomeController class (Controllers/HomeController.cs):

New actions share the same view – SignInOut.cshtml. The reason for this is that both pages have a very similar objective – display no content, just initialize some configurations and include a single JavaScript file.

Implementing the View

Before you can implement the view file, make sure the following JavaScript files are included in wwwroot/js directory:

The application will have only one view file, defined in the Views/Home directory (Views/Home/SignInOut.cshtml):

Use the ADAL.JS Library

You’re finally ready to use the ADAL.JS library. At first, you can develop the signin.js code (wwwroot/js/signin.js):

The Sign out script is even simpler (wwwroot/js/signout.js):

That’s it – 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 9100. You can configure it in Visual Studio in Project Properties as shown in Figure 6:

Figure 6: Configuring the App URL

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.

As you can see, the connection between the app and the extension is not implemented on the authorization app side. That’s the part that will be implemented on the extension side.

Enhancing the Extension: Content Script

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.

Chrome extensions can include a content script that runs in the context of web page loaded in the browser tab. That’s 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.

In this example, the ADAL.JS 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’t have access to the extension’s 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:

  • After successful login action
    • Message should include ADAL authorization data
  • After successful logout action.

All new functionality will make use of ADAL.JS and Chrome extensions APIs – functionalities that are not known for TypeScript. However, you can install additional packages to fix that issue:

You will also need a new configuration value stored in the environment collection. It’s necessary to include it in all instances of the environment class (src/environments/environment.ts):

It must also be included here, for example (src/environments/environment.prod.ts):

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 (src/app/common/services/common-account.service.ts):


As the content script consists of other modules, you can implement an additional one in a separate file (content-script/account-messenger.ts):


In my previous article, a content script code was introduced. The implementation was based on the boot.ts script in the content-script folder responsible for launching some classes with content script logic (RuntimeListener and ConnectListener). Since you inherited this solution, you can enhance it now to launch also AccountMessenger logic (content-script/boot.ts):

Finally, a provider of CommonAccountService needs to be registered in an Angular application as you’re about to inject it also into some components of the app. A reference to the service should be included in the AppModule class (src/app/app.module.ts):

Enhancing the Extension: Event Page Script

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:

  • signInAndRedirectToHomepage:
    • On this action store ADAL configuration values in extension local storage.
  • signOutAndRedirectToHomepage:
    • On this action clear ADAL configuration values from extension local storage.

After each action, the user should be redirected to the homepage. Implement this logic in the EventPageComponent class (src/app/event-page/event-page.component.ts).

Note: the decorators of the EventPageComponent class were generated automatically by the Angular CLI generate component command. If your project wasn’t 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 experimentalDecorators option in the tsconfig.json file. If your project doesn’t have such a file, you should create it in angular app root folder with the following content (tsconfig.json):

Secured Content

It’s 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 (src/app/homepage/homepage.component.html):

 

The last thing to do is to include the common.account.service module in homepage.component class and implement some logic there (src/app/homepage/homepage.component.ts):


Starting from now, the Chrome extension requires authorization of the user simply to show a content of content-container 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 gulp command and loading it as a chrome extension, you can see the following view on extension homepage shown in Figure 7:

Figure 7: The login page

After logging in, you can see the content available only for authorized users (Figure 8):

Figure 8: Content shown to authorized users

Summary

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 & 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.

Load comments

About the author

Jakub Kaczmarek

See Profile

I'm a software developer based in Wrocław, Poland; passionate about Microsoft technologies. My primary interests are web technologies such as ASP.NET/Core, MS SQL, Angular and Azure. I'm a member of Wrocław .NET community enthusiasts, interested in software craftsmanship conferences, meetups and lightning talks. Since 2017 I have been proudly representing Objectivity - the company that motivates me to share my knowledge.