angular: HttpClient HttpXsrfInterceptor does not set xsrf token for absolute urls?

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X ] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

In the HttpXsrfInterceptor xsrf.ts code, it is written in comments

// Skip both non-mutating requests and absolute URLs.
// Non-mutating requests don't require a token, and absolute URLs require special handling
// anyway as the cookie set
// on our origin is not the same as the token expected by another origin.
if (req.method === 'GET' || req.method === 'HEAD' || lcUrl.startsWith('http://') ||
      lcUrl.startsWith('https://')) {
      return next.handle(req);
}

So if the req.url starts with http or https, it does not set the header. What is the reason behind this? Both the wikipedia article on csrf or the owasp guide do not mention any special handling of absolute urls

Expected behavior

I think expected behavior should be the token should be added for all requests (except non-mutating ones) irrespective of being absolute urls

Environment


Angular version: 4.X

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 9
  • Comments: 20 (2 by maintainers)

Most upvoted comments

@jrhite until this is fixed you can write your own simple XSRFInterceptor as I did. As long as you are sure to call only one API there are no security implications through this approach.

@Injectable()
export class XsrfInterceptor implements HttpInterceptor {

    constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        let requestToForward = req;
        let token = this.tokenExtractor.getToken() as string;
        if (token !== null) {
            requestToForward = req.clone({ setHeaders: { "X-XSRF-TOKEN": token } });
        }
        return next.handle(requestToForward);
    }
}

With CORS you actually have to implement the TokenExtractor yourself as well…

A more sophisticated check for absolute URLs would be nice… Instead of checking for http:// or https:// it would be amazing to check if the absolute URL matches the URL the application is loaded from. In this case I guess it would be ok to automatically attach a XSRF header. This would resolve this issue for people using absolute URLs that point to the same server.

It seems to me that that it is necessary to improve the XSRF-support to become possible to use URLs starting with http(s)

Why do URLs starting with http(s) require special handling?

There are several possible situations:

  1. The address of the backend does not equals the address from which the application is downloaded (for example, the application is started in the dev environment using ng serve, and the backend is launched on another port/server, etc.)
  2. The application works with one or many servers that provide access to its API and each of them is not connected to each other and accordingly has its own sequence of XSRF-tokens (in this case even the names of the cookies in which the tokens are stored may differ)
  3. The application works with several subdomains of the 3rd or subsequent levels (api.example.com, api2.example.com, etc.), and the sequence of XSRF-tokens is global for all subdomains.

Thus, depending on the scenario, angular can support different strategies, for example:

  1. Each domain sends its own sequence of tokens not allied with each other’s domains. In this case, URLs not starting with http(s) are also considered as a separate domain.

or

  1. Subdomains of the 3rd or subsequent levels have a common sequence of tokens.

or

  1. All domains have a common sequence of tokens (is this even possible? And also issue above disables this behavior).

From the user’s point of view, it can look something like this:

HttpClientXsrfModule.withConfig({
    commonXsrfsequences: 'NONE' // SUBDOMAINS, ALL etc.
  }),

but in this case it is not possible to configure the cookie name for each domain individually.

It would be possible to give the user the ability to specify his own class that implements the HttpXsrfTokenExtractor interface (maybe this is possible?), But to work properly, it is necessary to delete the checks lcUrl.startsWith (‘http: //’) || lcUrl.startsWith (‘https: //’) in https://github.com/angular/angular/blob/54e02449549448ebab6f255f2da0b4396665c6f0/packages/common/http/src/xsrf.ts#L81-L82

@alxhub You can explain what the “and absolute URLs require special handling” comment means. Did I understand the problem correctly?

@haidelber Can you brief about tokenExtractor, I am getting token as null, Also i have tried to fetch token from cookie, code attached. I can see cookie in response of a first request. but while fetching i get document as empty.

 getCookie(name) {
    if (!document.cookie) {
        return null;
    }

    const xsrfCookies = document.cookie.split(';')
        .map(c => c.trim())
        .filter(c => c.startsWith(name + '='));

    if (xsrfCookies.length === 0) {
        return null;
    }

    return decodeURIComponent(xsrfCookies[0].split('=')[1]);
}

@otello1971, I just checked CLI and if I’m not wrong this proxy only refers to webpack dev server, which wouldn’t help in production if client-side and API are not on the same domain.

@netdeamon I suggest you add to the title that this is connected to HttpClient

“and absolute URLs require special handling” means that it’s up to the application to handle those URLs - the interceptor cannot know whether a given URL uses the same token, a different token, or no token at all. Providing an interceptor that can handle all of these cases is not in scope for common/http, but would be a great third-party library 😃

I think expected behavior should be the token should be added for all requests (except non-mutating ones) irrespective of being absolute urls

This has two problems:

  1. It leaks the security token by default to all origins, including third parties (yikes!)
  2. It breaks CORS by including an extra header that the server will likely not permit.

Also I found this issue #15052