Zhongfarewell

**[Chat] Reflections and Optimizations on the Identity Authentication Process in the Company's Development Environment**

Copying cookies is really stupid

#Scenario

Recently, the projects I’ve been involved in and the multiple development packages I used are all part of a large project. During development, to call interfaces normally on my local machine, I need to obtain login user information. Different people have different identities, and in certain business scenarios, it is necessary to frequently switch login users to perform operations corresponding to different identities.
To meet development needs, the company set up a page specifically for obtaining information of personnel with different identities (cookie, which is returned via response headers). Developers can directly click different identities or input a personnel ID on the page to initiate a simulated login request, and then retrieve the returned cookie information in the browser developer tools.
Of course, this is just the source of the cookie. To include it when making proxy interface requests, we need to create a cookie.txt file in the project root directory (convenient for locating, of course), and then configure the proxy to read the cookie information from that file before the request and append it to the request header. In this way, we successfully consume the cookie. The most tedious operation in the entire simulated login process is manually copying the response cookie and pasting it into the project’s cookie.txt file — not to mention that the cookie is only valid for one hour and there are frequent identity switches.

#Problem

In the scenario described above, we can clearly identify the problem: the development process interrupted by identity expiration and switching is very annoying. We don’t want to repeatedly copy cookies into various project files over and over. So I came up with the idea of using a Node script to simplify the cookie acquisition process.

This is an overly simple requirement, and you probably already have an implementation idea — use Node to send a request to the simulated login interface, parse the response headers to get the information, and write the response information into the project’s cookie.txt file. In fact, that’s exactly what I did. However, simulated login isn’t only used by developers; some non-developers also use it. For usability and consistent experience, the front-end simulated login page was directly copied from the company’s page (fortunately, the page is very simple with no external imports).
Now our login system has a front-end page.

#Exploration

We need to host this page for later use, and we try to keep the login system lightweight, so we avoid external dependencies as much as possible (for example, my first thought was to use Express to start the server).

Let’s run npm init -y to initialize an npm package.

Create a src folder to store our source code, name the copied front-end page index.html and put it inside. Create index.js, where we write code to start our port service and return the front-end page at the / path.

Access our port, and we successfully see the homepage of our “login system.”

The homepage has several quick login buttons and a user ID input box. Each button corresponds to a person with a different identity; clicking a quick button allows fast login without entering an ID, or you can enter a specific user ID in the input box to log in. These two functions actually correspond to the same interface — the difference is that the button operation includes the corresponding user ID in the request. In our login system, let’s name this interface path /login, and now implement it.

Our Node service currently only has the root / path, which returns the homepage. We need to parse the request URL in the Node service to match the /login path. In the handler for that path, we need to parse the login user ID parameter, generate request headers according to the company’s simulated login interface based on that parameter, and send the real login request. Note that simulated login uses the HTTPS protocol, so we should use Node’s https library when sending the request.

By debugging the interface and parsing the response headers, we can obtain the needed cookie. Next, we need to write the cookie into the corresponding project file. The path of the file we want to write to is unknown; after careful consideration, I decided to add a small cumbersome configuration item to our system — a .json file to store the addresses of files to be written. This means users must paste the paths of cookie.txt under each project into a JSON array before using it.

With this, we have implemented the whole process: open our login system, click login, and the corresponding cookie will be written into the target project’s cookie.txt. You no longer need to copy and paste cookies.

For identity expiration, our system has no way of knowing whether the cookie in the project has expired, because expiration only happens during proxy requests. One could judge during proxy requests… but invasively adding unnecessary code to modify proxy configurations is not desirable for anyone, so we still use a simple method: automatically re-simulate login after a fixed time to refresh the cookie.

Of course, the above is still just a rough description of the implementation approach; in actual development, more attention is needed to error handling and asynchronous processing.

#Extension

After using the above feature for a month, I found that sometimes I need to obtain cookies for a group of people or for someone whose name is known but ID is not, yet our system only supports login by ID and doesn’t support search. So I came up with the idea of mounting the personnel selector developed by a colleague onto the simulated login page.
This selector is wrapped based on Ant Design and supports searching and category filtering, which perfectly meets my needs. To mount it on the page, we need to package the component into an independent JS library and import it via <script> (limited by our index.html file).
Unfortunately, the component’s packaging tool is internally developed by the company and I’m not familiar with its configuration (even worse, there’s no documentation), so I decided to copy the source code and package it using Webpack.

#Running the Packaged Component

I previously wrote an article on manually configuring a React development environment. I modified this configured environment to make it more generic and pushed it to Gitee; here we simply clone that environment to package the component.

Copy the component’s source code into the project, replace the project’s dependency information with that from the component’s package.json, install dependencies, and start the project. Then, naturally, a bunch of errors appear.

My errors fall into two categories: TypeScript syntax errors and dependency version mismatches. The project uses a newer version of react-dom than the component, so after replacing dependencies and installing, I had to change the calling methods; TS syntax errors all come from declaration files of Ant Design components. Since packaging is just an intermediate step in development and the project runs fine after resolving dependency errors, I ultimately decided to ignore these errors and added the following configuration to ts-loader in the Webpack config to skip TS type checking:

js
options: { // Ignore TypeScript type checking errors transpileOnly: true }

#Component Mounting and Rendering

Simply exporting the component won’t make it run; we also need to consider how to render and mount it. We can import React-related dependencies via CDN, so that after the component loads in the browser, React rendering and mounting execute in the global environment. That’s a suitable solution.

However, here I placed the component mounting code inside the project, so the exported function can run directly without extra mounting code after the component loads on the page. For this, we need to additionally import the react-dom package.

#Packaging

Once the component debugging is done (of course, due to relative paths, the interface definitely won’t work), we can package it. Set Webpack’s output.libraryTarget="umd" and then run build.

#Simulated Content Distribution

Copy the packaged file into our login system and import it in index.html. Our resource package is actually stored locally, so we need to implement a simple static content distribution on our mini server. Here we add a new /assets path to handle all static file requests.
In the handler, we need to parse the requested path, locate the file, read the corresponding local file, and respond to the front end. Currently, we only have one static resource — the packaged component.

Restart our login system; the component package loads successfully, and our component renders properly on the page.

#Component Request Proxy

Requests inside the component are relative URLs, so we need to proxy these requests to the correct address to fetch data. The specific implementation is similar to the initial cookie acquisition method: intercept interfaces with the corresponding prefix, request the correct address, and return the data.

Restart the project, test whether the component interface works, and we’re done.

#Browser Extension Development

Our development above only applies to local project services (which fully meets the needs of local developers, of course), but testers’ testing work or project demos are usually done in the company’s development environment, where our local simulated service cannot be used.
Coincidentally, one day I heard a colleague mention browser extensions, and I got the idea of rebuilding the simulated login as an extension. I had no prior experience in browser extension development, so here I base the development on MDN’s documentation.

#Initializing the Extension Package

Thanks to ChatGPT, understanding certain concepts and API calls wasn’t too difficult. Following the documentation, we created manifest.json and made the necessary configurations, thus completing the initialization of the extension.
Next, we outline the extension’s features and functional points.

#Extension Design

As a plugin-provided feature, simulated login should be compact and easy to use. So after the extension is installed and running, we match the company environment domain via URL and inject a floating button into pages under those domains. Hovering the button pops up a personnel login button and a refresh button. Clicking the former opens a modal where you can search for personnel and log in with the corresponding identity; the refresh button is for re-logging into the last logged-in person after login expiration (identity validity is one hour).

#Injecting Our Component into the Page

For developing the component, we still choose React and Ant Design, and the packaging tool is the Webpack I used earlier. First, we need to clarify how to mount the component. Since we don’t know the exact structure of each extension page, we directly choose to mount the component to document.body. In our Webpack project, create a FloatButton folder at the same level as the Select component to store our final encapsulated component.
Import the required Ant Design components (Modal, FloatButton, Button, Message, Select, etc.). Place the Select component and a Button login button inside our Modal. When the user opens the modal, they can input the desired person’s name or employee ID in the Select. After selecting the target from the search results, they can click the login button to log in. The company already has an existing search interface; I simply parse the input parameters, call it, and parse the result back into the Select. When logging in, use fetch to call the company’s simulated login URL; the set-cookie response header in the response will let the browser set the cookie for us.

To implement the refresh function, we need to record the employee ID of the user’s last login; we store this in the browser’s local storage. Write it to local storage upon successful login, and read it when refreshing to request the login interface again.

After completing the business logic, we use react-dom in the component entry to render the component onto body and export it. After packaging, import the bundle under the content_scripts field in manifest.json.

Finally, upon successful login, we can help the user refresh the page, and we’re done.