Angular 5 unable to get XSRF token from HttpXsrfTokenExtractor
Asked Answered
I

2

16

I am trying to make a POST request via an absolute URL to a Spring (Basic authentication) secured Rest API.
Having read that Angular omits inserting the X-XSRF-TOKEN into the request header automatically for absolute urls, I tried to implement an HttpInterceptor to add the token in.

In my original /signin POST request, I create the necessary authorization: Basic header to ensure Spring authenticates the request.
The response header returned contains the expected set-cookie token:

Set-Cookie:XSRF-TOKEN=4e4a087b-4184-43de-81b0-e37ef953d755; Path=/

However, in my custom interceptor class when I try to obtain the token from the injected HttpXsrfTokenExtractor for the next request, it returns null.

Here is the code for my interceptor class:

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


@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {

   constructor(private tokenExtractor: HttpXsrfTokenExtractor) {}

   intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

     let requestMethod: string = req.method;
     requestMethod = requestMethod.toLowerCase();

     if (requestMethod && (requestMethod === 'post' || requestMethod === 'delete' || requestMethod === 'put' )) {
         const headerName = 'X-XSRF-TOKEN';
         let token = this.tokenExtractor.getToken() as string;
         if (token !== null && !req.headers.has(headerName)) {
           req = req.clone({ headers: req.headers.set(headerName, token) });
         }
      }

    return next.handle(req);
   }
}


tokenExtractor.getToken() returns null in the above code. I expected it to return the token from Spring (Set-Cookie) response header of my previous /signin request.

I read this related post for creating the interceptor:
angular4 httpclient csrf does not send x-xsrf-token

But I wasn't able to find much documentation for HttpXsrfTokenExtractor other than this:
https://angular.io/api/common/http/HttpXsrfTokenExtractor

Question: Why is HttpXsrfTokenExtractor.getToken() returning null?

In addition I added the interceptor class as a provider to the app.module.
Here is my app.module.ts:

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { LocationStrategy, HashLocationStrategy, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AlertModule } from 'ng2-bootstrap';
import { routing, appRouterProviders } from './app.routing';
import { HttpXsrfInterceptor } from './httpxrsf.interceptor';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './registration/register.component';
import { HomeComponent } from './home/home.component';

@NgModule({
    declarations: [AppComponent,
               LoginComponent,
               RegisterComponent,
               HomeComponent],
    imports: [BrowserModule,
          FormsModule,
          ReactiveFormsModule,
          HttpClientModule,
          HttpClientXsrfModule, // Adds xsrf support
          AlertModule.forRoot(),
          routing],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    providers: [
        appRouterProviders,
        [{provide: APP_BASE_HREF, useValue: '/'}],
        [{provide: LocationStrategy, useClass: HashLocationStrategy}],
        [{provide: HTTP_INTERCEPTORS, useClass: HttpXsrfInterceptor, multi: true }]
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}


Another thing to note is that I am running my Angular front end on Node.js from localhost:3000 and my Spring Rest back-end from localhost:8080. They are on different ports, and so the reason for making http requests with absolute urls. Would the browser prevent a Set-Cookie working when it comes from a response for a request on a different domain?

Could I be missing anything else?

Thank you for any help.

----------------------------------------------
[Updated 7th Jan 2018]

FIX

I use Webpack dev server to serve the Angular code. I worked around the issue by configuring a proxy for urls that point to my back-end Rest API.

This means all requests made from the browser now only be to the dev server at port 3000, even for the Rest API calls.
When the webpack dev server sees any request urls with the configured pattern (E.g /api/...), it replaces with calls to the back-end server on http://localhost:8080 (in my case).

This is my what I added to the devServer section of my webpack.dev.js file:

proxy: {
    '/api': {
    'target': 'http://localhost:8080',
    'pathRewrite': {'^/api' : ''}
    //Omits /api from the actual request. E.g. http://localhost:8080/api/adduser -> http://localhost:8080/adduser
    }
}



With this set up, I no longer make cross-domain (cross-origin) requests from the Angular code or use absolute URLs anymore. This hugely simplifies things as I am not fighting the Angular XSRF (CSRF) mechanism anymore. It just works by default. I also do not need to use an HttpInterceptor to manually insert the X-XSRF-TOKEN in either.

The added benefit of setting up a dev server proxy is that client requests are no longer absolute, so I do not need to change all the Rest API calls for production.

I hope this is useful for anyone who is suffering the same problem/understanding.

Webpack dev server proxy documentation ref:
https://webpack.js.org/configuration/dev-server/#devserver-proxy

Intercalary answered 28/12, 2017 at 6:28 Comment(0)
J
3

Since you are using different ports (3000 & 8080), you are making a cross-origin request, so you will not be able to read the cookie in the client sent from the server. If you want to separate your client and server in this way, you need to use a proxy so the client and server applications are served from the same protocol (http/https), domain, and port. If you are using Spring Boot I would suggest you look at Spring Cloud Netflix, specifically Zuul (https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_router_and_filter_zuul).

Justify answered 5/1, 2018 at 21:26 Comment(5)
Thank you for the suggestion and clarifying my understanding of cookies for cross origin requests. I am using Webpack dev server on the client side, so I managed to get around the problem by configuring a proxy for my rest calls. I will update my question to show what I did.Intercalary
Hi Glen I have the same problem, but I really don't understand what is the problem. I have develop a simple Angular Front-End that start from index.html. With Spring (core + security) are version 4.3.0 release i have depict the simple example with csrf enabled, but i nevers see the token neither from angular (5.2.11) neither from spring. Could you please give some help??Thanks a lotCarouse
@Carouse unless you are serving the angular app from the spring application or have a proxy you are probably making a cross origin request. I would suggest asking a new question and put all the details of your application in itJustify
@Justify Hi, I have integrate my angular app with spring, so all rest calls are in the same domain.Carouse
would you use this approach in production? Would you deploy the app using just proxy?Frustrate
I
0

Use withCredentials: true property inorder for it(XSRF-TOKEN) to be readable by angular code, just like below-

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
  withCredentials: true //this is required so that Angular returns the Cookies received from the server. The server sends cookies in Set-Cookie header. Without this, Angular will ignore the Set-Cookie header
}; 
Izzy answered 11/9, 2023 at 16:14 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.