38

I've read the docs and all the related questions on SO, but still Angular's XSRF mechanism isn't working for me: in no way I can make a POST request with the X-XSRF-TOKEN header appended automatically.

I have an Angular 6 app with a login form.

It's part of a Symfony (PHP 7.1) website, and the Angular app page, when served from Symfony, sends the correct Cookie (XSRF-TOKEN):

enter image description here

My app.module.ts includes the right modules:

// other imports...
import {HttpClientModule, HttpClientXsrfModule} from "@angular/common/http";

// ...
@NgModule({
  declarations: [
    // ...
  ],
  imports: [
    NgbModule.forRoot(),
    BrowserModule,
    // ...
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'XSRF-TOKEN',
      headerName: 'X-CSRF-TOKEN'
    }),
    // other imports
  ],
  providers: [],
  entryComponents: [WarningDialog],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Then, inside a Service's method, I'm making the following http request (this.http is an instance of HttpClient):

this.http
    .post<any>('api/login', {'_username': username, '_pass': password})
    .subscribe(/* handler here */);

The post request never sends the X-XSRF-TOKEN header. Why?

Arnaud Denoyelle
  • 29,980
  • 16
  • 92
  • 148
Paolo Stefan
  • 10,112
  • 5
  • 45
  • 64
  • I guess you could write your own interceptor to handle this. I know that some users have had issues with this when [not using absolute URLs](https://github.com/angular/angular/issues/20511#issuecomment-390918908) – Michael Doye May 24 '18 at 14:15
  • 1
    Thanks, I thought about it but I solved the problem in a cleaner way: plz see my answer. – Paolo Stefan May 24 '18 at 14:19
  • Hello Stefan, could you help me in generating XSRF Token value in an angular version 6 with PHP as my backend I couldn't able to var_dump the XSRF Token because as I'm unable to generate the token in typeScript [Click Here](https://stackoverflow.com/q/52342195/7713811) I have posted with this issue! – Nɪsʜᴀɴᴛʜ ॐ Sep 18 '18 at 10:10
  • @Nishanthॐ please have a look at your question, I've added an answer with a code sample. – Paolo Stefan Sep 18 '18 at 10:36

8 Answers8

53

The problem once again is Angular's poor documentation.

The fact is, Angular will add the X-XSRF-TOKEN header only if the XSRF-TOKEN cookie was generated server-side with the following options:

  • Path = /
  • httpOnly = false (this is very important, and fully undocumented)

Besides, the Angular app and the URL being called must reside on the same server.

Refer this Angular Github issue

Sangwin Gawande
  • 7,658
  • 8
  • 48
  • 66
Paolo Stefan
  • 10,112
  • 5
  • 45
  • 64
  • 5
    In addition to the above: - NO header is set for GET or HEAD requests - Name of cookie must be: XSRF-TOKEN (unless default name is overriden) - Most important make sure your not using absolute paths. With that it is meant paths that start with HTTP or HTTPS. It MUST be a RELATIVE path. – Merv Oct 19 '18 at 14:32
  • Does this mean that all of my server side requests must come from my sites domain instead of a dedicated api url? example: localhost:4000/home is the SPA and my apis would be at localhost:4000/api/getSomething instead of localhost:8080/api/getSomething – Anthony Nov 06 '18 at 21:49
  • @Anthony the cookie gets set from a server-side page, and gets read from a SPA, so, both the SPA and the api must be at least on the same 2nd level **domain**, so that the cookie can be read from the SPA. It could work if the server and the SPA run on different **ports** of the same domain, like in your example, but I'v not tested this scenario. – Paolo Stefan Nov 07 '18 at 11:15
  • @Merv So, why is GET not protected from XSRF? I'm new to understanding XSRF and may not understand it fully yet, but it seems that a malicious user could 'GET' data they shouldn't be able to. Could you clarify this for me? – Evan Sevy Sep 27 '19 at 15:48
  • @RuneStar That is true but by design, for this reason, you should never expose mutating requests on GET method. GET is particular because it is very easy to trick the user into executing a GET request by displaying an "image" which url is something nasty like `` – Arnaud Denoyelle Sep 27 '19 at 15:55
  • @ArnaudDenoyelle Thanks. I understand that you don't want to mutate within a GET because its not meant for that and, as you implied, could introduce a vulnerability if it did. But, I'm wondering if a malicious user can acquire the data that is returned back by a GET? Would it be returned to the original user, or would it be returned within the malicious users 'Cross Site Request'? – Evan Sevy Sep 27 '19 at 16:09
  • @RuneStar XSRF protection is not for protecting the web application from unauthorized access, meaning malicious users that gets data by a GET requests. XSRF protection is to protect your official users of your web application from malicious users who can modify/change data on your web application on behalf of your official users. And like Arnaud mentioned. A get request should never be used to modify data. Only to get data. If you want to protect ur app from getting data from malicious users u should look into securing ur URLs from the public with different roles privileges. PM for more info. – Merv Sep 27 '19 at 16:47
  • 1
    @Merv Say a malicious user somehow gets a victim to access their malicious website after the user has signed into my app. This malicious site has something like the following:
    . Is it possible for the data returned to be sent to the malicious user? To note, I am checking for authentication/authorization.
    – Evan Sevy Sep 27 '19 at 17:45
  • 1
    @RuneStar No its not possible (with only CSRF.) Because everything happens on the user's machine. So even if the user is deceived the response (with sensitive data) reaches the user's machine so the malicious user gains nothing from that. The only way the malicious user gains something is if he does(actions/modify/edit) something on the user's behalf that benefits him/her. Like maybe using your online banking to send money to his account. And the user will see the message (if the bank shows a message that money was sent) ....$ was sent to 'malicious user account'. I hope you understood! – Merv Sep 27 '19 at 23:52
  • @PaoloStefan, What is meant by Path = / . And how to set it if we have a Spring boot backend. – Jawadh Salih Rifath Nov 22 '19 at 12:03
34

On my team, the problem was that we were using an absolute path instead of a relative path.

So do not use an absolute path like:

this.http.post<any>("https://example.com/api/endpoint",data)

Use

this.http.post<any>("api/endpoint",data)

Or use

this.http.post<any>("//example.com/api/endpoint",data)

This is because absolute paths are explicitly ignored by Angular code on HttpClientXsrfModule (see)

Community
  • 1
  • 1
Leo
  • 621
  • 6
  • 9
  • 1
    Thanks for the reference to the Angular code. Makes sense now. – Gloire Apr 11 '20 at 11:55
  • Yeah this works. Can't be `http://...` because it doesn't set the header and can't be set without double slashes, at least not during `ng serve` because I get CORS error (my environments.ts file has a base API url of //example.test + port number, so that http requests can be tested on ng serve and after a build on my local machine) – OzzyTheGiant Apr 08 '21 at 15:38
  • Thanks a lot @Leo, I have been stuck on this problem for more than a month, before this i had disabled CSRF protection after being unable to find a solution online, today i came back to solve the problem and after trying many things this worked – pawn doe Aug 24 '22 at 08:21
8

After struggling for countless hours, the solution that worked for us was changing the request (in Angular) from 'https://example.com' to '//example.com'.

None of the other solutions worked for us.

Saunved Mutalik
  • 381
  • 2
  • 19
  • So far, this is the only solution that has worked for me. But I don't understand why. – Jason Powell Dec 14 '19 at 07:14
  • 1
    Because of [this](https://github.com/angular/angular/blob/f8096d499324cf0961f092944bbaedd05364eea1/packages/common/http/src/xsrf.ts#L77-L83) – yktoo Jun 03 '21 at 19:15
6

You should put on the service on the frontend this { withCredentials: true }

Ex:

this.http.put<class>(url, body, { withCredentials: true });
Vishwanath
  • 6,284
  • 4
  • 38
  • 57
John
  • 61
  • 1
  • 1
5

Slightly off topic, but for others who come here, I resolved this issue in the back end by the following (for spring-boot)

     /**
     * CORS config - used by cors() in configure() DO NOT CHANGE the METDHO NAME
     * 
     * @return
     */
    @Bean()
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Lists.newArrayList("http://localhost:4200"));
        configuration.setAllowedMethods(Lists.newArrayList("GET", "POST", "OPTIONS"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(Lists.newArrayList("x-xsrf-token", "XSRF-TOKEN"));
        configuration.setMaxAge(10l);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
Anand Rockzz
  • 6,072
  • 5
  • 64
  • 71
3

Make sure, your server allows X-CSRF-Token headers on when browser requests OPTIONS method.

Example:

Access-Control-Allow-Headers: X-CSRF-Token, Content-Type

Reference: MDN Docs

Ritwick Dey
  • 18,464
  • 3
  • 24
  • 37
2
    request = request.clone({
        withCredentials: true
      });

In InterceptService, this works with me

Abdallah
  • 139
  • 1
  • 13
0

For Spring Boot users this below taken me a while:

Besides, the Angular app and the URL being called must reside on the same server.

I was testing my solution on my localhost with apps on different ports and it works like it was the same origin.

But the problem happened after I changed context-path: /api and this was different from origin thats why I suppose Angular won't add XSRF token to request, so I need to add:

final CookieCsrfTokenRepository cookieCsrfTokenRepository =  CookieCsrfTokenRepository.withHttpOnlyFalse();
    cookieCsrfTokenRepository.setCookiePath("/");

to set up them same origin

Here is complex solution for diffrent context-path

Adriano
  • 874
  • 2
  • 11
  • 37