2

I'm working with several but obsolete documentation about implementing antiforgery token with angular. In my case I'm working without MVC Razor, only angular 13.3.4 and NET 6.0

I just make the configuration:

builder.Services.AddAntiforgery(options =>
{
    options.HeaderName = "X-XSRF-TOKEN";
});

builder.Services.AddScoped<AntiforgeryMiddleware>();

and then the controller:

public class AntiforgeryMiddleware : IMiddleware
    {
        private readonly IAntiforgery _antiforgery;

        public AntiforgeryMiddleware(IAntiforgery antiforgery)
        {
            _antiforgery = antiforgery;
        }

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            var isGetRequest = string.Equals("GET", context.Request.Method, StringComparison.OrdinalIgnoreCase);
            if (!isGetRequest)
            {
                _antiforgery.ValidateRequestAsync(context).GetAwaiter().GetResult();
            }

            await next(context);
        }
    }

but still can't get the thing with angular. My post is this one (just dummy to test it):

import { Component, Inject } from '@angular/core';
import { HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpXSRFInterceptor } from 'src/app/interceptors/tok.interceptor';

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: HttpXSRFInterceptor, multi: true }
]

@Component({
  selector: 'app-fetch-data',
  templateUrl: './fetch-data.component.html'
})
export class FetchDataComponent {
  public lenormandjack: LenormandHand = {} as LenormandHand;

  constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string, ) {
    http.post<LenormandHand>(baseUrl + 'api/lenormand', null, { withCredentials: true }).subscribe(result => {
      this.lenormandjack = result;
      console.dir(result);
      console.log("OK");
      console.dir(this.lenormandjack);
    }, error => console.error(error));
  }
}

I'm learning angular and I can't get a code that even compiles as typescript. Totally blocked and the documentation in several (dozens) of searchs returns the same. Or just for WebApi.

I'm trying to get working the antiforgery with this controller:

[HttpPost]
[ValidateAntiForgeryToken]
public LenormandHand Post()
{
    return foobar;
 
}

My interceptor:

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpXsrfTokenExtractor } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";

@Injectable()
export class XsrfInterceptor implements HttpInterceptor {

  constructor(private tokenExtractor: HttpXsrfTokenExtractor) { }

  private actions: string[] = ["POST", "PUT", "DELETE"];
  private forbiddenActions: string[] = ["HEAD", "OPTIONS"];

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let token = this.tokenExtractor.getToken();
    console.log ("TOKEN: "+token);
    let permitted = this.findByActionName(request.method, this.actions);
    let forbidden = this.findByActionName(request.method, this.forbiddenActions);;

    if (permitted !== undefined && forbidden === undefined && token !== null) {
      request = request.clone({ setHeaders: { "X-XSRF-TOKEN": token } });
    }

    return next.handle(request);
  }

  private findByActionName(name: string, actions: string[]): string | undefined {
    return actions.find(action => action.toLocaleLowerCase() === name.toLocaleLowerCase());
  }
}

UPDATE:

Following this: Anti forgery with token API and angular I could compile but but answer is 400 bad request. In deep, the header that contains X-XSRF-TOKEN is always false

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Leandro Bardelli
  • 10,561
  • 15
  • 79
  • 116
  • 1
    Possibly this question may help: [Anti forgery with token API and angular](https://stackoverflow.com/q/53086456/8017690) – Yong Shun Jul 17 '22 at 03:30
  • Thanks @YongShun I read it and close it in sometime because I couldnt use the angular code, but I will try again with that response to get in closer to the solution – Leandro Bardelli Jul 17 '22 at 03:35
  • [Here's](https://pieterjandeclippel.medium.com/asp-net-core-angular-xsrf-62c3833fd1fe) a quick, good and thorough walkthrough on how to set it up – Pieterjan Jul 17 '22 at 17:25
  • Oh and apart from that, you also have to ensure that all calls that return a xsrf token are protected. In MVC this means that GET requests returning a `
    ` with a xsrf token need to be protected using **CORS** so that other websites cannot fetch it using ajax. In SPA's this means that the `X-XSRF-TOKEN` cookie, which can be read using javascript, MUST NOT be readable from other websites
    – Pieterjan Jul 17 '22 at 17:47
  • Thanks @Pieterjan . In this scenario I'm not using MVC or html form. That's the problem behind everything. I already post the answer, the problem was in the declaration of the app module in angular. – Leandro Bardelli Jul 17 '22 at 19:25

1 Answers1

1

I finally did it. There are a lot of changes (and not) and there are a lot of considerations you have to take into account in order to implement the AntiForgery with security.

I will make a tutorial because I lost a lot of time to resolve this request/issue.

The code from question itself I made is just fine, so you can use it as a base.

DO NOT EVEN try the "controller way" to generate a token. It is a non-sense because everyone can call it.

  • The invoke should have the following cookie options:

    Path = "/",
    HttpOnly = false,
    Secure = true,
    SameSite = SameSiteMode.Strict
    
  • The setup only needs this configuration in the Startup.cs:

    builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
    
  • And this configuration:

    app.UseAntiforgeryToken();
    

The heavy metal part: the call should be not absolute:

I use "api" here because I only want to secure the controllers that includes "api" in the endpoint to have more flexibility.

constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string, ) {
    http.post<yourclass>(baseUrl + 'api/controller', null, { withCredentials: true }).subscribe(result => {
      this.yourinstance = result;
    }, error => console.error(error));
  }
}
  • In app.module.ts file add this:

    import { XsrfInterceptor } from 'src/app/interceptors/tok.interceptor';
    

and then INSIDE the @NgModule({ section:

  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: XsrfInterceptor, multi: true }
  ],

See my complete module file:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { CounterComponent } from './counter/counter.component';
import { FetchDataComponent } from './fetch-data/fetch-data.component';

import { XsrfInterceptor } from 'src/app/interceptors/tok.interceptor';


@NgModule({
  declarations: [
    AppComponent,
    NavMenuComponent,
    HomeComponent,
    CounterComponent,
    FetchDataComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    HttpClientModule,
    FormsModule,
    RouterModule.forRoot([
      { path: '', component: HomeComponent, pathMatch: 'full' },
      { path: 'counter', component: CounterComponent },
      { path: 'fetch-data', component: FetchDataComponent },
    ])
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: XsrfInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • If you want to use this only for some kind of controllers, I also separate them with "api" prefix:

So my controller has this route:

[ApiController]
[Route("api/[controller]")]

and my method looks like this:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public Method Post()
    {

You can make an Invoke and check if the path contains 'api' or not. Several examples of this implementation have it because the example is a web api (totally optional, per gusto e piacere).

  • If you are in development mode with a reverse proxy, don't forget to add the "api" to the proxy settings if you are using the same method like me.

You add this to proxy.config.js

const PROXY_CONFIG = [
  {
    context: [
      "/imgs",
      "/api"
   ],
Joan Gil
  • 282
  • 3
  • 11
Leandro Bardelli
  • 10,561
  • 15
  • 79
  • 116