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());
}
}
Any help will be preciated, comment, question, answer, anything. Thanks in advance
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
CodePudding user response:
Finally did it. There is a lot of changes (and not) but there is a lot of considerations you have to take in order to implement this with security.
I'll enumerates and made a tutorial because I lost a lot of time to resolve this request/issue.
The question itself I made is fine, so you can the code for the answer from the question itself.
DO NOT EVEN try the "controller way" to generate a token. Is a non-sense because everyone can call it.
The invoke should has the following cookie options:
Path = "/", HttpOnly = false, Secure = true, SameSite = SameSiteMode.Strict
The setup only needs:
this:
builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
and this:
app.UseAntiforgeryToken();
- The heavy metal part: the call should be not absolute:
I use "api" here because I only want to secure the controllers with "api" so I can choose what and what don't.
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 }
],
like in total:
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:
[ApiController]
[Route("api/[controller]")]
and my method is like this:
[HttpPost]
[ValidateAntiForgeryToken]
public Method Post()
{
So you can make an Invoke checking if the the path has api or not. Several examples of this implementation in everywhere has it because is for web api. This is totally optional, per gusto e piacere.
- If you are in development mode with a proxy reverse, don't forget 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"
],