简体   繁体   中英

.Net Core: Validate Anti Forgery Token with Ionic front end

I have looked all over and have found similar solutions, but nothing that matches exactly what I'm working on.

We have a .net core MVC website with an API Controller for handling requests from an ionic mobile app which we are also developing.

In most cases, adding [ValidateAntiForgeryToken] to the API controller actions works. I have gone through the process of generating the token, passing it to Ionic, and storing it in the request headers for validation.

Here is the code I am using to fetch and store the token:

static XSRF_TOKEN_KEY: string = "X-XSRF-TOKEN";
static XSRF_TOKEN_NAME_KEY: string = "X-XSRF-TOKEN-NAME";

constructor(){}

static getXsrfToken(http: HTTP) : {tokenName: string, token: string} {
    let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
    let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
    if(!tokenName || !token){
        this.fetchXsrfToken(http);
        tokenName= window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
        token = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
    }
    return {
        tokenName: tokenName,
        token: token
    };
}

private static setXsrfToken({ token, tokenName }: { token: string, tokenName: string }) {
    window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_KEY, token);
    window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_NAME_KEY, tokenName);
}

private static fetchXsrfToken(http: HTTP) {
    let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
    let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);

    if (!token || !tokenName) {
        let apiUrl: string = AppConfig.apiUrl + "/GetAntiforgeryToken";
        http.get(apiUrl, {}, {})
            .then(r => this.setXsrfToken(JSON.parse(r.data)))
            .catch(r => console.error("Could not fetch XSRFTOKEN", r));
    } else {
        this.setXsrfToken({ token: token, tokenName: tokenName });
    }
}

Here is the action in my controller that serves anti forgery tokens:

[HttpGet]
public override IActionResult GetAntiforgeryToken()
{
    var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
    return new ObjectResult(new
    {
        token = tokens.RequestToken,
        tokenName = tokens.HeaderName
    });
    }

I set the headers of the http plugin by calling this function from the view's associated typescript file:

initializeHttp() {
    let token = ValidationManager.getXsrfToken(this.http);
    this.http.setHeader(token.tokenName, token.token);
    console.log("Http Initialized: ", token);
}

then any request I make with the http plugin is validated properly in the controller's action:

this.http.post(apiUrl, {}, {}).then(response => {
   that.navCtrl.setRoot(HomePage);
});

Up to this point, everything works great. The problem arises when I try to use XmlHttpRequest to for a POST instead of the built-in http plugin:

let file = {
    name: e.srcElement.files[0].name,
    file: e.srcElement.files[0],
  };

  let formData: FormData = new FormData();
  formData.append('file', file.file);

  let xhr: XMLHttpRequest = new XMLHttpRequest();
  xhr.open('POST', apiUrl, true);
  console.log("setting request header: ", tokenVal); //verify that tokenVal is correct
  xhr.setRequestHeader("X-XSRF-TOKEN", tokenVal);
  xhr.send(formData);

If I remove the [ValidateAntiForgeryToken] attribute from the controller's action, the file is posted properly. However, nothing I have tried has worked with the attribute being included.

I believe the issue has something to do with the validation tokens being added to a cookie automatically by Ionic, and the cookie is passed along with the request from the http plugin. However, XMLHttpRequest does not pass the cookie along (and is unable to do so?).

I have read up on the subject quite a bit over the past few days but I admit that this validation is still mostly a black box to me. Is there a way to validate the request in my action using only the token which is passed up in the header?

The reason I am running into this problem is that I need to upload a file, which I was unable to do using the http plugin. There are solutions for uploading images using Ionic's file-transfer plugin, but it has been deprecated and the release notes suggest using XmlHttpRequest instead.

Other things I have tried:

  • I have found solutions for .net standard which use System.Web.Helpers.AntiForgery for custom validation on the server, but this namespace is not included in .net core and I could not find an equivalent.
  • I tried many different ways to post the file using the http plugin (since it has no issues validating the antiForgery token). Everything I tried resulted in the action being hit but the file being posted was always null . A solution which uploads a file using the http plugin would also be acceptable.

Why is it that I was able to spend two full days on this problem, but as soon as I post a question about it, I find the answer? Sometimes I think the internet gods are just messing with me.

As it turns out, the native http plugin has an uploadFile() function that I never saw mentioned anywhere else. Here's what the solution does:

  1. Use the fileChooser plugin to select a file from the phone's storage
  2. Use the filePath plugin to resolve the native filesystem path of the image.
  3. Use http.uploadFile() instead of http.post()

This works because as mentioned above, I was able to properly set the validation token in the http plugin's header to be accepted by the controller.

And here is the code:

let apiUrl: string = AppConfig.apiUrl + "/UploadImage/";
this.fileChooser.open().then(
  uri => {
    this.filePath.resolveNativePath(uri).then(resolvedPath => {
      loader.present();
      this.http.uploadFile(apiUrl,{ },{ },resolvedPath, "image")
      .then(result => {
        loader.dismiss();
        toastOptions.message = "File uploaded successfully!";
        let toast = this.toastCtrl.create(toastOptions);
        toast.present();
        let json = JSON.parse(result.data);
        this.event.imageUrl = json.imgUrl;
      })
      .catch(err => {
        console.log("error: ", err);
        loader.dismiss();
        toastOptions.message = "Error uploading file";
        let toast = this.toastCtrl.create(toastOptions);
        toast.present();
      });
    });
  }
).catch(
  e => console.log(e)
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM