Handling an authentication page returned by an axios request in vue

感情迁移 提交于 2020-03-03 07:08:25

问题


I have a vue app that sits behind a firewall, which controls authentication. When you first access the app you need to authenticate after which you can access the app and all is well until the authentication expires. From the point of view of my app I only know that the user needs to re-authenticate when I use axios to send off an API request and instead of the expected payload I receive a 403 error, which I catch with something like the following:

import axios  from 'axios'
var api_url = '...'

export default new class APICall {
    constructor() {
        this.axios = axios.create({
            headers: {
                'Accept': 'application/json',
                'Cache-Control': 'no-cache',
                'Content-Type': 'application/json',
            },
            withCredentials: true,
            baseURL: api_url
        });
    }
    // send a get request to the API with the attached data
    GET(command) {
        return this.axios.get(command)
            .then((response) => {
                if (response && response.status === 200) {
                    return response.data; // all good
                } else {
                    return response;      // should never happen
                }
            }).catch((err) => {
                if (err.message
                    && err.message=="Request failed with status code 403"
                    && err.response && err.response.data) {
                    // err.response.data now contains HTML for the authentication page
                    // and successful authentication on this page resends the
                    // original axios request, which is in err.response.config
                }
            })
    }
}

Inside the catch statement, err.response.data is the HTML for the authentication page and successfully authenticating on this page automatically re-fires the original request but I can't for the life of me see how to use this to return the payload I want to my app.

Although it is not ideal from a security standpoint, I can display the content of err.response.data using a v-html tag when I do this I cannot figure out how to catch the payload that comes back when the original request is fired by the authentication page, so the payload ends up being displayed in the browser. Does anyone know how to do this? I have tried wrapping everything inside promises but I think that the problem is that I have not put a promise around the re-fired request, as I don't have direct control of it.

Do I need to hack the form in err.response.data to control how the data is returned? I get the feeling I should be using an interceptor but am not entirely sure how they work...

EDIT

I have realised that the cleanest approach is to open the form in error.response.data in a new window, so that the user can re-authenticate, using something like:

var login_window = window.open('about:blank', '_blank');
login_window.document.write(error.response.data)

Upon successful re-authentication the login_window now contains the json for the original axios get request. So my problem now becomes how to detect when the authentication fires and login_window contains the json that I want. As noted in Detect form submission on a page, extracting the json from the formatting window is also problematic as when I look at login_window.document.body.innerText "by hand" I see a text string of the form

JSON
Raw Data
Headers
Save
Copy
Collapse All
Expand All

status  \"OK\"
message \"\"
user    \"andrew\"

but I would be happy if there was a robust way of determining when the user submits the login form on the page login_window, after which I can resend the request.


回答1:


One solution is to override the <form>'s submit-event handler, and then use Axios to submit the form, which gives you access to the form's response data.

Steps:

  1. Query the form's container for the <form> element:

    // <div ref="container" v-html="formHtml">
    const form = this.$refs.container.querySelector('form')
    
  2. Add a submit-event handler that calls Event.preventDefault() to stop the submission:

    form.addEventListener('submit', e => {
      e.preventDefault()
    })
    
  3. Use Axios to send the original request, adding your own response handler to get the resulting data:

    form.addEventListener('submit', e => {
      e.preventDefault()
    
      axios({
        method: form.method,
        url: form.action,
        data: new FormData(form)
      })
      .then(response => {
        const { data } = response
        // data now contains the response of your original request before authentication
      })
    })
    

demo




回答2:


I would take a different approach, which depends on your control over the API:

  • Option 1: you can control (or wrap) the API
    1. have the API return 401 (Unauthorized - meaning needs to authenticate) rather than 403 (Forbidden - meaning does not have appropriate access)
    2. create an authentication REST API (e.g. POST https://apiserver/auth) which returns a new authentication token
    3. Use an Axios interceptor:
this.axios.interceptors.response.use(function onResponse(response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // no need to do anything here
    return response;
  }, async function onResponseError(error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    if ("response" in error && "config" in error) { // this is an axios error
      if (error.response.status !== 401) { // can't handle
        return error;
      }
      this.token = await this.axios.post("auth", credentials);
      error.config.headers.authorization = `Bearer ${this.token}`;
      return this.axios.request(config);
    }
    return error; // not an axios error, can't handler
  });

The result of this is that the user does not experience this at all and everything continues as usual.

  • Option 2: you cannot control (or wrap) the API
    1. use an interceptor:
this.axios.interceptors.response.use(function onResponse(response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // no need to do anything here
    return response;
  }, async function onResponseError(error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    if ("response" in error && "config" in error) { // this is an axios error
      if (error.response.status !== 403) { // can't handle
        return error;
      }
      if (!verifyLoginHtml(error.response.data)) { // this is not a known login page
        return error;
      }
      const res = await this.axios.post(loginUrl, loginFormData);
      return res.data; // this should be the response to the original request (as mentioned above)
    }
    return error; // not an axios error, can't handler
  });



来源:https://stackoverflow.com/questions/60327960/handling-an-authentication-page-returned-by-an-axios-request-in-vue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!