Image for post
Image for post

Single sign in or how to share data in your browser across domains

Maxime Thomasson
Sep 11, 2020 · 9 min read

Every modern browser offers mechanisms to store data that can be retrieved when opening a new window or a new tab. The different storage options are cookies, local storage, and session storage. The problem is that these storage options are segmented per domain (or subdomain). That means that xyz.com will not be allowed to access any of the information that abc.com has set. And browsers don’t provide any options to share data across domains, for obvious security and data privacy reasons.

At Bloom Partners, we build digital solutions that drive long-term consumer engagement through content delivery. This often requires our users to use several web apps, that connect to the same authentication API. Those web-apps may not use the same framework, as the purpose of the app drives what platform it is based on.

Considering that a secure API only allows a few active authentication tokens, if your user needs to use more apps than the number of available tokens allowed by the API, he will not be able to stay logged in all the apps he needs to use. Therefore he will need to go through the authentication process multiple times a day.

The article is a step by step guide on a solution we built at Bloom Partners to answer this problem in a secure, framework-agnostic manner, using Webpack and Typescript only.

Overview of the solution

The previously mentioned snippet will also register a few methods (set, retrieve, delete) on the window object, adding a layer between the iframe and your application to streamline the communication between them.

The compiled result will be composed of

  • A “snippet.js” script, registering the iframe on the index.html, and providing the methods to the window object. The methods provided to the window object should return promises, that will be resolved or rejected when the snippet receives specific events from the iframe.
  • An index.html, that will import its script: “iframe.js”. This script will listen to the events coming from the snippet and send events in response.

The communication between the script and the iframe will be done through the window.postMessage interface.

As a good picture is worth a thousand words, here is a time diagram showing the path of an application requesting an authentication token to the sign in script:

Image for post
Image for post
Time diagram showing the communication between a frontend application, the single sign application and the API

Step by step guide

1. Project setup and minimal Webpack configuration

A “src/” folder containing 3 folders:

  • iframe/, that will contain the code related to the iframe: We can already initialize it with an index.html file and an “index.ts” file referenced in the index.html.
  • snippet/ that will contain the code related to the single sign-in initialization (spawning the iframe on the page), and the code related to the snippet itself. We can already initialize it with an “index.ts” file.
  • A shared folder containing our models, Enums, and any piece of code that needs to be shared between the 2 elements

As you’ve seen in the previous step, we created some typescript files, so we need to configure typescript adding a basic “tsconfig.json” (see Here link or preview to the tsconfig.json file)

It is now time to build the Webpack configuration. We are going to need the following Webpack plugins:

  • HtmlWebpackPlugin: generates the HTML file of the iframe. We will pass it as argument the template (index.html file previously created), and add the “excludeChunks” parameter, to exclude the snippet’s script
  • ForkTsCheckerWebpackPlugin to speed up the type check
  • ShakePlugin

We also need to specify our entries and outputs: We have 2 independent entries: “src/iframe/index.ts”, and “src/snippet/index.ts”. And we want those 2 entries to be compiled in 2 files, so the output parameter of the Webpack configuration should look like:

 output: {
filename: ‘[name].js’,
path: path.resolve(__dirname + ‘/dist’)
}

This will result in the generation of 2 JavaScript files: iframe.js and snippet.js

And the last step is to create two NPM tasks in the package.json, one that builds the project for production purposes (minified and optimized), and another that serves it in a development server.

In order to have a running development server, we will use webpack-dev-server

After this step, your repository should look like the following Github repository: Project setup

Everything compiles properly and running “npm run build” builds the 3 files that we need: snippet.js, iframe.js, and index.html, referencing our iframe.js file. However, there is no logic in those files.

2. Spawn the iframe on the page

The “createIframe” function creates the HTML element using the document.createElement web API sets its source and id, and a couple of styling elements to make sure that it is not visible on the page. Once this is done, it appends it to the document’s body using the appendChild method.

And we will call this “createIframe” method from the main function of the index.ts file.

After this step, your repository should look like the following Github repository

We now have a basic Webpack configuration and a script that spawns the iframe on the app’s index.html file. It is now time to add the business logic.

3. Registration of events in the iframe

The iframe will listen to three different events, with a self-explanatory name: GET_AUTH_TOKEN, STORE_AUTH_TOKEN (with the authentication token in the parameters), and DELETE_AUTH_TOKEN. and will send an event to its parent window with the response.

You can see in the last commit of this part, that I added a few models to properly type the message event data, and the “snippet-events.enum.ts”, that defines all the events that the snippet can send. Those events are the ones that we will be listening to in the iframe.

After this step, your repository should look like the following Github repository.

4. Registration of the methods in the window element in the snippet

However, in a Typescript environment, the window interface is set and does not contain “singleSignIn” element part of its data model. We therefore need to overwrite this window interface, declaring it globally. You can check the following stack overflow link for more information on how to extend the window object data model.

Let’s now create a “MethodsService” class, that contains a method to register the functions on the window object. It contains the 3 methods that we want to attach to the window element, and a function to register them on the window object. The last step is now to call this registration function from the main function in the “index.ts” file in the snippet’s repository.

After this step, your repository should be like the following Github repository.

5. Send events to the iframe from the snippet and answer in the iframe’s script

In the method service (snippet), when one of the window methods is called, we register a one-time listener that will listen for one of the 2 outputs that the iframe can send: Positive or negative response: For example, in the case of “getAuthToken”, we will react if the message sent by the iframe is “GET_AUTH_TOKEN_RESPONSE” or “GET_AUTH_TOKEN_ERROR”. once the listener is registered, we will send to the iframe the event named “GET_AUTH_TOKEN” and return a promise. The promise is either resolved or rejected in the handler method.

In the EventListenerService, we access the local storage and we simply answer to the snippet using “window.parent.postMessage()”

After this step, your repository should be like the following: Github repository

6. Implementation of a demo HTML file that would use the snippet the same way you could use it in an app

  • The first one simply imports our snippet.js file
  • The second one tries to store, retrieve, and delete an authentication token.

We do not need to serve that file through a Webserver, we can simply open it in the browser from the file system.

It is however necessary to have the single sign-in webserver running, as the script is imported through your localhost.

7. Secure the connection between the frame and the snippet

You can check the final code here

Conclusion and potential improvements

However, this solution still misses a few key points in order to be production-ready:
First, we should set up a linter like “tslint”, if multiple people start to work on the project. It would also be good to add a testing framework, and tests to improve the robustness of the solution, and avoid regressions when adding new features. But I did not add this part in the tutorial to avoid adding extra complexity.

In the core features, another improvement could be added:
For now, we don’t really know when the iframe and snippets finished loading. So, we need to wait a little while to be sure that both are loaded. A good improvement would be to set up a “queue” mechanism in the snippet and to register the methods to the window object at the very beginning. We could then avoid having to wait in the app to do the first request.

Finally, we only support the storing n authentication token as a string format. Some use cases may require you to share some more complex data. This is not an issue: You could easily create some more methods and events to do so.

If you have any comments, suggestions, or questions, don’t hesitate to drop a comment! I’d be happy to answer and improve the solution!

Bloom Partners

Creating digital growth.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store