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 will not be allowed to access any of the information that has set. And browsers don’t provide any options to share data across domains, for obvious security and data privacy reasons.

每种现代的浏览器都提供了存储可以在打开新窗口或新选项卡时检索的数据的机制。 不同的存储选项是cookie,本地存储和会话存储。 问题在于这些存储选项是按域(或子域)分段的。 这意味着xyz.com将不允许访问abc.com设置的任何信息。 而且出于明显的安全性和数据隐私性原因,浏览器不提供任何跨域共享数据的选项。

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.

在Bloom Partners,我们建立了数字解决方案,通过内容交付来推动长期的消费者参与。 这通常要求我们的用户使用多个连接到相同身份验证API的Web应用程序。 这些Web应用程序可能不会使用相同的框架,因为该应用程序的目的是驱动该应用程序所基于的平台。

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.

考虑到安全的API仅允许使用几个活动的身份验证令牌,如果您的用户需要使用的应用数量超过了API允许的可用令牌数量,则他将无法保持登录所需的所有应用程序的身份。 因此,他将需要每天多次进行身份验证过程。

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.

本文是在Bloom Partners上构建的解决方案的分步指南,该解决方案仅使用Webpack和Typescript以安全的,与框架无关的方式回答了此问题。

解决方案概述 (Overview of the solution)

The main idea is to have a domain that all your applications can share. In order to do that, you will develop a JavaScript snippet, that will spawn an iframe on the page. The domain of this iframe will be the one used to share data across your different web apps.

主要思想是拥有一个所有应用程序都可以共享的域。 为此,您将开发一个JavaScript代码段,该代码段将在页面上生成一个iframe。 此iframe的域将是用来在您不同的Web应用程序之间共享数据的域。

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.

    “ snippet.js”脚本,将iframe注册在index.html上,并将方法提供给window对象。 提供给window对象的方法应返回承诺,当代码段从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.

    一个index.html,它将导入其脚本:“ iframe.js”。 该脚本将侦听来自摘要的事件,并发送事件作为响应。

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
Time diagram showing the communication between a frontend application, the single sign application and the API

逐步指南 (Step by step guide)

Let’s go deeper in the details of the implementation. If at any point you are lost, you can check up the finished version of this in the following repository: Github repository

让我们深入了解实现的细节。 如果您迷路了,可以在以下存储库中查看此文件的完成版本: Github存储库

1.项目设置和最少的Webpack配置 (1. Project setup and minimal Webpack configuration)

The first step is to create a new directory, initialize NPM (“npm init”), and create our file structure, which will be composed of:

第一步是创建一个新目录,初始化NPM(“ npm init”),并创建我们的文件结构,该文件结构将包括:

A “src/” folder containing 3 folders:

包含3个文件夹的“ src /”文件夹:

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

    iframe /,它将包含与iframe相关的代码:我们已经可以使用index.html文件和index.html中引用的“ index.ts”文件对其进行初始化。
  • 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.

    snippet /,其中将包含与单一登录初始化相关的代码(在页面上生成iframe)以及与代码片段本身相关的代码。 我们已经可以使用“ index.ts”文件对其进行初始化。
  • 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)

如您在上一步中所见,我们创建了一些打字稿文件,因此我们需要配置打字稿,添加一个基本的“ tsconfig.json”(请参阅​​此处链接或预览到tsconfig.json文件)。

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

现在该构建Webpack配置了。 我们将需要以下Webpack插件:

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:

我们还需要指定条目和输出:我们有2个独立的条目:“ src / iframe / index.ts”和“ src / snippet / index.ts”。 而且我们希望将这两个条目编译为两个文件,因此Webpack配置的输出参数应类似于:

 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

完成此步骤后,您的存储库应类似于以下Github存储库: 项目设置

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.

一切都可以正确编译,并运行“ npm run build”来构建我们需要的3个文件:snippet.js,iframe.js和index.html,引用我们的iframe.js文件。 但是,这些文件中没有逻辑。

2.在页面上生成iframe (2. Spawn the iframe on the page)

The first thing we need to do is to spawn the iframe on the page, during the initialization process of the snippet.js. For this, we’ll create an “iframeUtils” class in a separate file in the snippet folder that contains a “createIframe” and a “getIframe” method.

我们需要做的第一件事是在snippet.js的初始化过程中在页面上生成iframe。 为此,我们将在片段文件夹中的单独文件中创建一个“ iframeUtils”类,其中包含“ createIframe”和“ getIframe”方法。

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.

“ createIframe”函数使用document.createElement Web API创建HTML元素,并设置其源代码和ID,以及几个样式元素,以确保其在页面上不可见。 完成此操作后,它将使用appendChild方法将其附加到文档的正文中。

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

我们将从index.ts文件的主要功能中调用此“ createIframe”方法。

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.

现在,我们有了基本的Webpack配置和一个脚本,该脚本在应用程序的index.html文件上生成iframe。 现在该添加业务逻辑了。

3.在iframe中注册事件 (3. Registration of events in the iframe)

Like the “snippet.ts“ file, the “iframe.ts” file contains a “main” function that runs on load. This method registers the different event listeners, with a callback.

像“ snippet.ts”文件一样,“ iframe.ts”文件包含一个在加载时运行的“ main”功能。 此方法使用回调注册不同的事件侦听器。

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.

iframe将侦听三个不同的事件,它们的名称不言自明:GET_AUTH_TOKEN,STORE_AUTH_TOKEN(在参数中带有身份验证令牌)和DELETE_AUTH_TOKEN。 并将带有响应的事件发送到其父窗口。

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.

您可以在本部分的最后一次提交中看到,我添加了一些模型来正确键入消息事件数据,以及“ snippet-events.enum.ts”,它定义了代码片段可以发送的所有事件。 这些事件是我们将在iframe中收听的事件。

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


4.在代码段的window元素中注册方法 (4. Registration of the methods in the window element in the snippet)

The goal of this part is to provide the different methods to create, retrieve, and delete an authentication token, so that an application that uses it can simply call “window.singleSignIn.getAuthToken()” and receive a promise as response.

本部分的目的是提供创建,检索和删除身份验证令牌的不同方法,以便使用该令牌的应用程序可以简单地调用“ window.singleSignIn.getAuthToken()”并接收promise作为响应。

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.

但是,在Typescript环境中,设置了窗口界面,并且不包含其数据模型的“ singleSignIn”元素部分。 因此,我们需要覆盖此窗口界面,以全局方式对其进行声明。 您可以检查以下堆栈溢出链接,以获取有关如何扩展窗口对象数据模型的更多信息。

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.

现在,让我们创建一个“ MethodsService”类,其中包含一个在窗口对象上注册功能的方法。 它包含我们要附加到window元素的3个方法,以及一个将其注册到window对象的函数。 现在的最后一步是从代码段存储库中的“ index.ts”文件中的主函数调用此注册函数。

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


5.从代码段向iframe发送事件,并在iframe脚本中进行回答 (5. Send events to the iframe from the snippet and answer in the iframe’s script)

We have the frame for the communication between the iframe and the snippet, it is now time to send messages. In that part, we will fill all the functions in the “MethodsService” and the “EventListener” service.

我们拥有用于iframe和代码段之间通信的框架,现在是时候发送消息了。 在那部分中,我们将填充“ MethodsService”和“ EventListener”服务中的所有功能。

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.

在方法服务(代码段)中,当调用一种窗口方法时,我们注册一个一次性侦听器,该侦听器将侦听iframe可以发送的2个输出之一:正响应或负响应:例如,在这种情况下如果是iframe发送的消息是“ GET_AUTH_TOKEN_RESPONSE”或“ GET_AUTH_TOKEN_ERROR”,我们将做出React。 注册侦听器后,我们会将名为“ GET_AUTH_TOKEN”的事件发送到iframe并返回承诺。 在处理程序方法中可以解决承诺或拒绝承诺。

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

在EventListenerService中,我们访问本地存储,我们只需使用“ window.parent.postMessage()”来答复代码段。

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

完成此步骤后,您的存储库应如下所示: Github存储库

6.实现演示HTML文件,该HTML文件将使用与您在应用程序中使用代码段相同的方式 (6. Implementation of a demo HTML file that would use the snippet the same way you could use it in an app)

The whole business logic is now done, so let’s create a static index.html file at the root of our repository that uses our snippet. This file only contains 2 script tags:

现在已经完成了整个业务逻辑,因此让我们在使用代码段的存储库根目录中创建一个静态index.html文件。 该文件仅包含2个脚本标签:

  • 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.固定框架和代码段之间的连接 (7. Secure the connection between the frame and the snippet)

We now have a fully functional single sign in handling. However, we don’t want any external pages to be able to use it, or any malicious script that would try to get the authentication information of our platform. To avoid that, the iframe is going to check the domain of the parent page and compare it with a list of white-labeled domains stored in its code. If the parent page is not from an authorized domain, we don’t set up the event listeners on the iframe side. That implies that the requests from the snippet will simply be ignored, and the promises never fulfilled. Therefore, the malicious website cannot access the information that we stored.

现在,我们有了功能齐全的单一登录处理。 但是,我们不希望任何外部页面都可以使用它,也不希望任何试图获取我们平台的身份验证信息的恶意脚本。 为了避免这种情况,iframe将检查父页面的域,并将其与存储在其代码中的带有白色标签的域的列表进行比较。 如果父页面不是来自授权域,则我们不会在iframe端设置事件监听器。 这意味着摘录中的请求将被简单地忽略,并且承诺从未实现。 因此,恶意网站无法访问我们存储的信息。

You can check the final code here


结论和潜在的改进 (Conclusion and potential improvements)

We went through the different steps needed to create a single sign-in application. Now, if you have two web apps importing the snippet script, they can both access the same browser storage and thus share an authentication token. The user only needs to sign in one application, and he gets redirected to the authenticated area in all the other apps. All the apps use the same authentication token, which therefore has no chance to get invalidated.

我们经历了创建单个登录应用程序所需的不同步骤。 现在,如果您有两个导入片段脚本的Web应用程序,则它们都可以访问相同的浏览器存储,从而共享身份验证令牌。 用户只需要登录一个应用程序,即可将他重定向到所有其他应用程序中的已验证区域。 所有应用程序都使用相同的身份验证令牌,因此没有机会失效。

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.

但是,该解决方案仍然缺少一些关键点才能投入生产:首先,如果有多个人开始从事该项目,我们应该建立一个像“ tslint”之类的套子。 添加测试框架,并进行测试以提高解决方案的健壮性,并在添加新功能时避免退化,这也将是一件好事。 但是我没有在教程中添加此部分,以避免增加额外的复杂性。

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.

在核心功能中,可以添加另一个改进:目前,我们还真的不知道iframe和代码段何时完成加载。 因此,我们需要稍等片刻以确保两者均已加载。 一个很好的改进是在代码片段中建立“队列”机制,并在一开始就将方法注册到window对象。 然后,我们可以避免在应用程序中等待执行第一个请求。

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.

最后,我们仅支持将n个身份验证令牌存储为字符串格式。 一些用例可能需要您共享一些更复杂的数据。 这不是问题:您可以轻松创建更多方法和事件。

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!

如果您有任何意见,建议或问题,请不要犹豫! 我很乐意回答并改善解决方案!



  • 0
  • 0
  • 0
  • 一键三连
  • 扫一扫,分享海报

评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
钱包余额 0