Reputation: 11598
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
Upvotes: 4
Views: 4639
Reputation: 11598
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 { }
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).
You add this to proxy.config.js
const PROXY_CONFIG = [
{
context: [
"/imgs",
"/api"
],
Upvotes: 3