Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Boot - CSRF headers #495

Open
Oddisey opened this issue Nov 27, 2018 · 11 comments
Open

Spring Boot - CSRF headers #495

Oddisey opened this issue Nov 27, 2018 · 11 comments

Comments

@Oddisey
Copy link

Oddisey commented Nov 27, 2018

I am receiving a CSRF token from my backend, putting the CSRF in local storage and then adding that CSRF manually to the headers in the "watch" when I am trying to subscribe to a topic (see below)

        this.authenticationService.csrf().pipe(
            tap((csrf: Csrf) => {
                const connectHeaders = {};
                connectHeaders[csrf.headerName] = csrf.token;
                this.rxStompService.watch("/topic/notify", connectHeaders).subscribe((message: Message) => {
                    console.log(JSON.parse(message.body));
                });
            })
        );

My issue is that I am not seeing the CSRF token in the headers. How do I add the CSRF token to the socket connection so that it is sent over in the handshake headers the same way that it is sent over in the an http call.

@kum-deepak
Copy link
Member

Please enable debug by setting the following in the configuration and attach your console output please.

      debug: (str) => {
        console.log(new Date(), str);
      }

@Oddisey
Copy link
Author

Oddisey commented Nov 28, 2018

Can do, I'll have that over in just a bit.

@Oddisey
Copy link
Author

Oddisey commented Nov 28, 2018

I actually need to rephrase this question. I need to add the CSRF to a "publish", not a "watch". So the publish looks like so:

this.rxStompService.publish({ destination: "/app/sendMessage", body: JSON.stringify({ message: "foo" }), headers: { "X-CSRF-TOKEN": "foo"} });

And I am getting the following in the console:

---------------------------------------------------------------------------------------------
Wed Nov 28 2018 10:39:16 GMT-0500 (Eastern Standard Time) "<<< ERROR
content-length:0
---------------------------------------------------------------------------------------------
message:Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.web.csrf.InvalidCsrfTokenException\c Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
content-length:0
---------------------------------------------------------------------------------------------

Now I know that "foo" is not a valid CSRF, it's just a placeholder. But what the backend seems to be reading in Spring Security is a null value. Which tells me that it is not reading the CSRF in the header (or as a parameter) at all.

@kum-deepak
Copy link
Member

Not sure, might be issue at your server configuration. I do not use Spring at all.

However, from previously raised issues and questions - I had imagined that typically CSRF header would be set at CONNECT frame (connectHeaders at ng2-stompjs). Please check Spring documentation: https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/websocket.html

@Oddisey
Copy link
Author

Oddisey commented Nov 29, 2018

I have tried setting the CSRF token in the headers by deactivating the connection, then resetting the configuration and then reconnecting, but ng2-stomp seems to be making a connection before I can reconfigure the RxStompService to have the CSRF in the headers.

Is there a way for the service to not auto-connect when injected and wait for me to reconfigure?

@kum-deepak
Copy link
Member

There are actually multiple ways to achieve this:

    stompService.configure(config);
    stompService.activate();
  • You can use APP_INITIALIZER to fetch the configuration in a way that Angular boot process waits till fetch completes. A sample using older version of this library is at https://github.com/stomp-js/ng2-stompjs-demo. The API has significantly changed but you can see how the Angular boot process waits till HTTP request to fetch configuration completes.

Either of the above will work. If you find these complicated, please let me know. I will write a small guide covering these approaches.

@Oddisey
Copy link
Author

Oddisey commented Nov 29, 2018

I tried that like so:

{
    provide: InjectableRxStompConfig,
    useValue: StompConfig
},
{
    provide: RxStompService,
    useFactory: rxStompServiceFactory
}

but I got the following error:

Uncaught Error: Can't resolve all parameters for rxStompServiceFactory: (?).

@Oddisey
Copy link
Author

Oddisey commented Nov 29, 2018

I have just provided the RxStompService and just not used a factory at all. Seeing if that works

@Oddisey
Copy link
Author

Oddisey commented Nov 29, 2018

Is there any way to proxy the connection?

@Oddisey
Copy link
Author

Oddisey commented Nov 29, 2018

I figured it out. For anyone else who finds this thread helpful and is having the same issue. I was able to proxy by using a docker instance of nginx to host the angular app, and then proxy_pass the websocket in the nginx config in the following way

default.conf

server {
    location /api/ws {
        proxy_pass http://[some_ip]:8080/api/ws;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}

stomp.config

export const MyStompConfig: InjectableRxStompConfig = {
    // Which server?
    brokerURL: "ws://localhost/api/ws",

    // Headers
    // Typical keys: login, passcode, host
    connectHeaders: {
        login: "guest",
        passcode: "guest",
    },

    // How often to heartbeat?
    // Interval in milliseconds, set to 0 to disable
    heartbeatIncoming: 0, // Typical value 0 - disabled
    heartbeatOutgoing: 20000, // Typical value 20000 - every 20 seconds

    // Wait in milliseconds before attempting auto reconnect
    // Set to 0 to disable
    // Typical value 500 (500 milli seconds)
    reconnectDelay: 200,

    // Will log diagnostics on console
    // It can be quite verbose, not recommended in production
    // Skip this key to stop logging to console
    debug: (msg: string): void => {
        console.log(new Date(), msg);
    },
};

then I also just provided the RxStompService instead of using the factory provider, like so:

change

providers: [
    {
        provide: RxStompService,
        useFactory: rxStompServiceFactory,
        deps: [InjectableRxStompConfig]
    },
    ...
]

to

providers: [
    RxStompService
]

Lastly in the actual place that you want to use the service. Inject normally:

constructor(private rxStompService: RxStompService)

and then configure it, and start it:

        const config: InjectableRxStompConfig = { ...MyStompConfig, connectHeaders: { "X-CSRF-TOKEN": [put your CSRF token here] } };
        this.rxStompService.configure(config);
        this.rxStompService.activate();

        // you can now publish
        this.rxStompService.publish({ destination: "/app/sendMessage", body: JSON.stringify({ message: "foo" }), headers });

        // and subscribe
        return this.rxStompService.watch("/topic/notify", headers).pipe(
            tap((message: Message) => {
                context.patchState({ something: JSON.parse(message.body) });
            })
        );

@kum-deepak
Copy link
Member

Great, the code looks alright. In Nginx proxy, increase the timeout. WebSockets may send data after significant gaps. I usually set it to many hours.

One additional advantage of using Nginx to proxy - it allows setting up HTTPS at Nginx proxy.

@kum-deepak kum-deepak changed the title CSRF headers Spring Boot - CSRF headers Dec 23, 2018
@kum-deepak kum-deepak transferred this issue from stomp-js/ng2-stompjs Jan 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants