I am building out my first Rust client/server application and struggling to get my client/server talking to each other over a websocket connection.
Any help would be highly appreciated!
Problem Overview
- I am using axum with axum-login on the server.
- I am using reqwest and ewebsock on the clients.
- I am using websockets to transport data between the server and connected clients.
- The client code needs to build and run on wasm and native targets.
- Everything works perfectly with the wasm client.
- The native client fails when opening the websocket conneciton with a
401 Unauthorized
.
Connection process
The first three steps are exactly the same for wasm and native.
Wasm client (works)
- Client send HTTP POST request to
http://localhost:8080/login
with login credentials - Login succeeds.
- Client opens websocket connection via
ws://localhost:8080/websocket
using ewebsock crate. - Connection opens successfully.
Native client (fails)
- Client send HTTP POST request to
http://localhost:8080/login
with login credentials - Login succeeds.
- Client opens websocket connection via
ws://localhost:8080/websocket
using ewebsock crate. - Connection rejected with a
401 Unauthorized
response.
I can not understand why the 401
is happening only for the native client.
I suspect an issue with storing the users authentication cookie but as far as I can tell, ewebsock (which depends on tokio-tungstenite) does not read the browser cookies while opening the websocket connection anyways (?).
Maybe someone who has a working user auth system with an axum web server using websockets as the transport layer could shed some light on how they got things working?
Relevant dependencies
Client
- reqwest to make http calls to backend to authorize the user.
- enabled cookie_store on native
- ewebsock to establish a websocket connection once the user has authenticated.
Server
- axum (with feature [“ws”])
- axum-login for authentication.
Code
Servers main function - axum::Router (paired down):
let app = Router::new()
.route("/websocket", get(websocket_handler))
.route_layer(RequireAuthorizationLayer::<UserId, User, UserRole>::login())
.route("/login", post(login_handler))
.layer(TimeoutLayer::new(Duration::from_secs(4)))
.layer(auth_layer)
.layer(session_layer)
.layer(CorsLayer::permissive())
.layer(LiveReloadLayer::new())
.with_state(app_state);
pub async fn websocket_handler(
ws: WebSocketUpgrade,
State(app_state): State<AppState>,
) -> impl IntoResponse {
ws.on_upgrade(|socket| websocket(socket, app_state.websocket_state))
}
How can I fix the 401 Unauthorized
response on native?
Other notes
- If I remove the requirement for authorization, both the wasm and native clients connect successfully.
- I am using axum_sessions for session management (removed this code from the snippet above).
- I still strongly suspect the issue is with cookie management - but I am not sure how to open this connection elegantly (i.e. using the same code paths) for both wasm and native.
I have tried enabling the tls
feature on ewebsock
crate and changing the connection url to wss://localhost/websocket. This fails.
I have tried enabling the cookie_store in reqwest
- but this has no effect. I do not beleive there is a way for ewebsock
to access that information anyways.