React DataManager: How to resubmit a data request after a failed request (401 Unauthorized)? (RefreshToken scenario)

Hi,
I would like to ask how I can modify (extend) the DataManager, especially the WebApiAdaptor, to resubmit the data request after a (first) failed request.

Let me describe you briefly our scenario:
We are using a JWT Token for authentication, which we add to each request in the header. Since the token is only valid a few minutes, we also have a refresh token, which is stored as a cookie and can be use to obtain a new valid JWT Token from the backend. Now we have the following problem, for example: A Grid retrieves some records from the backend with the initial valid token over the DataManger (using the WebApiAdaptor). After a few minutes we want to refresh our Grid. Unfortunately, the grid refresh-request failes, and the backend returns a 401 error (Unauthorized), since the token is expired and not anymore valid. Now I want to do the following: First, I want to catch this type of error (which I am already able using the onFailure-event of the Ajax "settings" object in the beforeSend method of the WebApiAdaptor), then I want to refresh my "old" JTW token making a"refreshToken" request to the backend, and on a successful token refresh I want to resubmit the initial Grid request, but with the new token in the header, which I got from my intermediate "refreshToken" step. How can I do that?

In addition, I would like to ask, if you have any hint, how to avoid an infinite loop of requests of type "refreshToken", when the request of the "resubmission" again fails, to avoid entering again in the "refreshToken" procedure...

Summary: How can I resend a data request from the WebApiAdaptor after a failure? Especially, how can I do that from inside the "onFailure"-event of the "settings" Ajax object of the "beforeSend" method (WebApiAdaptor)? Or is there any other method of the WebApiAdaptor, which have to override for this purpose?


export class DWebApiAdaptor extends WebApiAdaptor {
  beforeSend(dmDataManagerrequestXMLHttpRequestsettingsAjax): void {
    const failure = settings.onFailure;
    settings.onFailure = function (data) {
      if (data && data["status"] !== undefined) {
        if (data["status"] === 401) {
            axios.post(`/api/refreshtoken`)
              .then(response => {
                request.setRequestHeader("Authentication"`Bearer ${response.data.Token}`);
                settings.httpRequest = request;
                this.beforeSend(dmrequestsettings); //--> HERE SHOULD BE THE RESUBMIT, BUT DOES NOT WORK IN THIS WAY
              })
              .catch(err => {
                logout();
              });
          } else {
            logout();
          }
        }
      }
      failure.call(thisdata);
    };
    super.beforeSend(dmrequestsettings);
  }

  ...
}


Thanks.


3 Replies 1 reply marked as answer

PG Praveenkumar Gajendiran Syncfusion Team March 25, 2021 10:52 AM UTC

Hi Laurin, 

Thanks for contacting Syncfusion support. 

Based on your query we could see that if your current JWT token has expired/failed, you need to make an async call to get a new one before the request was sent to the server. For this we suggest you to use any custom API to validate the header token. For validating the token with a custom API, Please refer the below sample to achieve your requirement. 
 
In this sample, we have override our makeRequest method and make a own AJAX call. When our own AJAX call gets success, we have send the Datamanager request to the server. So you can also send the Datamanager request in your custom API call success method. Please refer the below code example and sample for more information. 

 
export class FetchEmployee extends React.Component<RouteComponentProps<{}>, FetchEmployeeDataState> { 
    
    constructor(props) { 
        super(props);        
    } 
    public onload(args: any) { 
        (DataManager.prototype as any).makeRequest = function (url: any, deffered: Deferred, args?: any, query?: any): any { 
         .  .  .  .  .  .  .  . 
         .  .  .  .  .  .  .  .    
        let req: Object = (this as any).extendRequest(url, fnSuccess, fnFail); 
        let ajax: Ajax = new Ajax(req); 
        let getInstance: any = this; 
        ajax.beforeSend = () => { 
            (this as any).beforeSend(ajax.httpRequest, ajax); 
        }; 
        let customajax: Ajax = new Ajax(); 
        customajax.type = 'POST'; 
        (customajax as any).contentType = 'application/json'; 
        customajax.url = '/Home/Data'; 
        customajax.data = JSON.stringify({ gid: [{ OrderID: 1009, CustomerID: "Raja", ShipCity: "India" }] }); 
            customajax.send().then(function (value: any) { 
                debugger; 
            req = ajax.send(); 
            (req as any).catch((e: Error) => true); // to handle failure remote requests. 
            (getInstance as any).requests.push(ajax); 
 
        }); 
        .  .  .  .  .  .  .  . 
        .  .  .  .  .  .  .  . 
    } 
        let dm: any = new DataManager({ 
            url: 'api/Orders', 
            adaptor: new WebApiAdaptor 
        }); 
        (this as any).grid.dataSource = dm; 
    } 
 
    public render() { 
        return (<div className='control-section'> 
            <GridComponent ref={g => (this as any).grid = g}  allowPaging={true}  height={340} load={this.onload.bind(this)} > 
                <ColumnsDirective> 
                    <ColumnDirective field='OrderID' headerText='Order ID' width='125' textAlign='Right' /> 
                    <ColumnDirective field='ShipCity' headerText='ShipCity' width='125' /> 
                </ColumnsDirective> 
                <Inject services={[Page]} /> 
            </GridComponent> 
        </div>) 
    }        
} 
 
 

We have prepared a sample based on this for your reference. 

Regards, 
Praveenkumar G 


Marked as answer

LS Laurin S March 25, 2021 03:09 PM UTC

Thank you very much for the quick response. I have two questions:

1. Is there a way to handle my issue also from the beforeSend method?
2. Is there any other solution that is a little bit easier (i.e. where I don't have to override the complete makeRequest-method)?

Thanks, Cheers.


PG Praveenkumar Gajendiran Syncfusion Team March 26, 2021 08:48 AM UTC

Hi Laurin, 
Thanks for your update. 
Based on your query, we would like to inform you that the beforeSend method of Grid is not an asynchronous function. So we are not able to handle/perform an asynchronous operation inside the beforeSend method. This method is used only to override/customize the request headers and the attributes.

Documentation: https://ej2.syncfusion.com/react/documentation/data/how-to/#adding-custom-headers

The ‘http’ URl request is generated in the makeRequest method of Grid that’s why we suggested you to handle your requirement in the makeRequest method.

Please get back to us if you need further assistance.

Regards,
Praveenkumar G
 


Loader.
Up arrow icon