Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 5 unable to get XSRF token from HttpXsrfTokenExtractor

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

like image 225
Glen Au-Yeung Avatar asked Dec 28 '17 06:12

Glen Au-Yeung


1 Answers

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).

like image 81
dspies Avatar answered Nov 10 '22 03:11

dspies