This article introduces some practical tips for dealing with access_token handling in front-end API calls, including: chain calls for Promises, interceptors for AXIos, vue-Router logging navigation history, and more.

Reference items: github.com/jasony62/tm…

Problem analysis

In a project where the front and back ends are completely separated, a front-end application accesses multiple back-end apis, and the API calls are authenticated by passing tokens. The user login is to exchange the user name and password for token. After obtaining the token, the front-end retains it (for example, puts it in sessionStorage), and then adds this parameter every time the USER initiates an API call. To ensure security, the token has a validity period. After the validity period expires, you need to log in again to obtain a new token. We can see that the core of user login process design is actually a problem of managing and using tokens.

Based on token usage, the following situations need to be considered:

  • The page is displayed, and multiple API calls are made simultaneously on the page
  • Users perform operations on the page and initiate multiple API calls during the operations, such as submitting a form
  • Call API, local no token, open login page
  • The login page is displayed after the API is invoked and the token has expired
  • Call API, other request is getting token, stop or cancel call
  • Call authentication API without adding token
  • After the login is successful, save the obtained token, close the login, and invoke the API again

There are several technical problems:

  • Front-end local storage token;
  • Distinguish whether the API calls need to add tokens;
  • If multiple requests are made at the same time, how to cancel or suspend the other requests when one of them has started to obtain the token;
  • How to automatically return to the previous status after login.

Understand the Axios interceptor

In order to satisfy the above requirements, you need to be able to control the execution of API requests. In AXIos, you add control logic through interceptors, because let’s take a closer look at the code for interceptors in AXIos.

// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);

this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
  chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
  chain.push(interceptor.fulfilled, interceptor.rejected);
});

while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}

return promise;
Copy the code

To understand the code above, first understand the promise chain call and promise.then().

Chain calls

The prmise chain call just strings together several promises, and the result of the last promise execution serves as the input for the next promise. Here’s an example:

let p1 = Promise.resolve('a')
let p2 = Promise.resolve('b')
let p3 = Promise.resolve('c')
let p4

p4 = p1.then(v1 => {
  console.log('then-1'V1) // This is line 2 output, output Areturn p2.then(v2 => v1 + v2)
}).then(v1 => {
  console.log('then-2', v1) // This is line 3 output, which prints abreturn p3.then(v2 => v1 +v2)
})

p4.then(v => {
  console.log('then-3', v) // This is line 4 output, output ABC}) console.log('begin... ') // This is line 1 outputCopy the code

In this way, multiple asynchronous operations can be executed in series.

Then method

The Promise’s then method passes in two arguments, which are called when the Promise object calling the THEN method completes or fails. Note that this call is asynchronous (queued), which is why the last sentence console.log() is the first output in the above example, because the callback function in then is queued.

The key to mastering the THEN method is to understand the return value. First, the then method returns a Promise object, which is the basis for chain calls. Second, which callback function to execute is determined by the result of the execution of the Promise object that calls THEN (there is no relationship between the two callback functions). Third, the state of the returned Promise object is determined by the return value of the callback function executed (regardless of which callback function returns). For example, if the callback function returns a value (number, string, object, etc.), the generated Promise object will be fulfilled. For details, see the online documentation.

It is important to note that the failed callback is only the result of the currently executed Promise object, not the result of the entire chain. Both completion and failure callbacks can return a value that tells the next link whether to enter the completion or failure function. Thus, each link in the chain call can fix the “error” of the previous link and continue the chain execution.

Here’s an interesting question: is catch necessarily the last thing to be executed besides finally? Can it be written before then? The answer is yes. Catch =>{then(undefined, err=>{… }).

Reference: developer.mozilla.org/zh-CN/docs/…

Axios interceptor

With the chain calls and then methods in mind, the axios interceptor mechanism is easy to understand.

while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}
Copy the code

This is a big pity, which is a pity. Each interception rule is composed of a function (fulfilled) and a function (rejected), which can be interpreted as: What should I do if the last step of the request succeeds and what should I do if it fails. This understanding is critical because it is easy to think of adding interception rules as adding interception logic to the completion function, and if the logic fails, processing it in the failure function. An exception occurs in the completion function, and the failed function is not executed because it is not called by the completion function, but by the result of the previous execution. The exception that completes the function is handled in the failure function of the subsequent link.

Also, it is important to note that request rules and response rules are executed in a different order, with request rules defined first and then executed (unshift) and response rules defined first and then executed (push).

Furthermore, the request rule and the response rule are on the same chain, so exceptions in the request rule can be handled by the response-phase failure function. For example, if the user needs to be given a message box to explain whatever is wrong with the execution of the request, even exceptions that occur during the request phase can be handled uniformly in response interception rules.

Local Storage Token

After obtaining the token, you can store it in localStorage or sessionStorage, for example:

sessionStorage.setItem('access_token', token)
Copy the code

Distinguishes whether the API adds a token

Axios supports the creation of new instances, and you can assign different interception rules to different instances.

 axios.create(config)
Copy the code

An AXIOS instance can be named in a TMS-Vue project and different interception rules can be specified.

Vue.TmsAxios({ name: 'file-api', rules })
Vue.TmsAxios({ name: 'auth-api' })
Copy the code

Suspending API calls

By setting interception rules, we can control the front-end process of API calls.

When the API is called around the token, an AXIos request may encounter two situations: 1. The request phase discovers that the token does not exist, obtains the token, and continues to send; 2. The token returned in the response phase is unavailable. After the token is obtained, the request is resend. If multiple apis are called “simultaneously” and the current request has already started retrieving tokens, the request should all be suspended, waiting for a new token, and the token should not be acquired repeatedly.

We can think of token acquisition as an operation that requires “lock” control, that is, only the first request can obtain the lock, and the subsequent requests are locked, waiting for the result of the execution of the first request. Once the first request completes, subsequent requests will all get the result, thus avoiding repeating the token retrieval operation with each request.

The Promise mechanism can well meet the above requirements. The basic idea is that we make the login a Promise, and all requests wait for the Promise to be executed. Add rule to request interceptor (hint) :

function onFulfilled(config) {
  ......
  if (requireLogin) return loginPromise
  ......
}
Copy the code

LoginPromise allows you to suspend the AXIos request until the login completes.

The key issue here is that loginPromise must be shared, and all requests that are happening must wait for the same Promise. This is a big pity. However, because the token has a period of validity, the user may need to login several times during the whole use process. Once the loginPromise is implemented once, it will be fulfilled, and the subsequent invocation will not initiate a new login. To solve this problem, you need to remove the loginPromise after all pending requests are notified that the login is complete, and generate new promises when new requests are made.

To solve this problem, the lock-Promise component is implemented in TMS-Vue.

onst RUNNING_LOCK_PROMISE = Symbol('running_lock_promise')

class TmsLockPromise {
  constructor(fnLockGetter) {
    this.lockGetter = fnLockGetter
    this.waitingPromises = []
  }
  isRunning() {
    return!!!!! this[RUNNING_LOCK_PROMISE] }wait() {
    if(! this.isRunning()) { this[RUNNING_LOCK_PROMISE] = this.lockGetter() }letPROM = new Promise(resolve => {this[RUNNING_LOCK_PROMISE]. Then (token => Enclosing waitingPromises. Splice (this) waitingPromises) indexOf (PROM), 1) / / all requests are processed, close the logging resultsif (this.waitingPromises.length === 0) {
          setTimeout(() => {
            this[RUNNING_LOCK_PROMISE] = null
          })
        }
        resolve(token)
      })
    })
    this.waitingPromises.push(prom)

    return prom
  }
}

export { TmsLockPromise }
Copy the code

The call code is as follows:

let lockPromise = new TmsLockPromise(function() {// return a promise to wait for execution results, such as login})...let pendingPromise = lockPromise.wait() 
Copy the code

At the heart of the lock-PROMISE component is the wait method. Each time this method is called, a new Promise object is created and the “proxy” waits for the result of the login so that it can perform some state-management operations.

Lock-promise allows you to suspend requests that are initiated “simultaneously” (during login) and wait for the “lock” action to complete before continuing to execute all requests. The lock status is automatically cleared after all requests have been executed.

Return to the previous state after login

The previous section looked at processing tokens when an API call has already been made. We can also check whether the token is present before entering the page. If not, we can jump to the login page. After login, we can return to the page to enter. This approach is suitable for first-time applications.

Vue-router is used to achieve this function. First, it is checked by the navigation guard mechanism. Second, after a successful login, you should be able to automatically return to the page the user intended to visit.

To solve this problem, the router-history plug-in is implemented in TMS-Vue.

router.beforeEach((to, from, next) => {
  if(to.name ! = ='login') {// Instead of accessing the login page, check the tokenlet token = sessionStorage.getItem('access_token')
    if(! Token) {Vue. TmsRouterHistory. Push (to. The path) / / save the original page jumpreturn next('/login') // Skip to login page}} next()}Copy the code

After successful login, check whether you want to return to the original page:

if (this.$tmsRouterHistory.canBack()) {
  this.$router.back()
} else {
  this.$router.push('/')}Copy the code

conclusion

Promise is the most important concept, it is the implementation of many complex schemes of the underlying mechanism, must be mastered!!

To solve the above problems, the “API+ login” to solve the key technical problems of authentication, but still need to be refined, such as: login component design, failure handling, etc.. These issues will be explored in subsequent articles.

Other articles in this series:

Vue project summary (1) – basic concepts +Nodejs+ Vue +VSCode

Vue Project Summary (2) – Front-end independent test + Vue

Vue Project Summary (3) – Analysis of cross-domain problems caused by front-end and back-end separation