diff --git a/tutorial/server/axum/Cargo.toml b/tutorial/server/axum/Cargo.toml index 5a2c8cc1..3a029b2e 100644 --- a/tutorial/server/axum/Cargo.toml +++ b/tutorial/server/axum/Cargo.toml @@ -12,17 +12,15 @@ license = "MPL-2.0" tracing.workspace = true tracing-subscriber.workspace = true serde.workspace = true -rand = { workspace = true, features = ["min_const_gen"] } webauthn-rs = { workspace = true, features = ["danger-allow-state-serialisation"] } axum = {version = "0.6.1", features = ["http2"]} -axum-extra = { version = "0.4.2" , features = ["spa"]} tokio = { workspace = true, features = ["full"] } uuid = { workspace = true, features=["v4"] } url.workspace = true thiserror.workspace = true -axum-sessions = "0.4.1" tower = "0.4.13" -tower-http = {version="0.3.4", features=["fs"]} +tower-http = {version="0.4.4", features=["fs"]} +tower-sessions = "0.6" [features] default = ["wasm"] diff --git a/tutorial/server/axum/assets/auth.js b/tutorial/server/axum/assets/js/auth.js similarity index 100% rename from tutorial/server/axum/assets/auth.js rename to tutorial/server/axum/assets/js/auth.js diff --git a/tutorial/server/axum/assets/js_index.html b/tutorial/server/axum/assets/js/index.html similarity index 100% rename from tutorial/server/axum/assets/js_index.html rename to tutorial/server/axum/assets/js/index.html diff --git a/tutorial/server/axum/assets/wasm_index.html b/tutorial/server/axum/assets/wasm/index.html similarity index 100% rename from tutorial/server/axum/assets/wasm_index.html rename to tutorial/server/axum/assets/wasm/index.html diff --git a/tutorial/server/axum/build_wasm.sh b/tutorial/server/axum/build_wasm.sh index 73845fa2..6e2c6435 100755 --- a/tutorial/server/axum/build_wasm.sh +++ b/tutorial/server/axum/build_wasm.sh @@ -1,2 +1,2 @@ #!/bin/sh -wasm-pack build ../../wasm --out-dir ../server/axum/assets --target web \ No newline at end of file +wasm-pack build ../../wasm --out-dir ../server/axum/assets/wasm --target web diff --git a/tutorial/server/axum/src/auth.rs b/tutorial/server/axum/src/auth.rs index 7d690988..b98f98ae 100644 --- a/tutorial/server/axum/src/auth.rs +++ b/tutorial/server/axum/src/auth.rs @@ -5,7 +5,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; -use axum_sessions::extractors::WritableSession; +use tower_sessions::Session; /* * Webauthn RS auth handlers. @@ -51,7 +51,7 @@ use webauthn_rs::prelude::*; pub async fn start_register( Extension(app_state): Extension, - mut session: WritableSession, + session: Session, Path(username): Path, ) -> Result { info!("Start register"); @@ -76,7 +76,7 @@ pub async fn start_register( }; // Remove any previous registrations that may have occured from the session. - session.remove("reg_state"); + session.remove_value("reg_state"); // If the user has any other credentials, we exclude these here so they can't be duplicate registered. // It also hints to the browser that only new credentials should be "blinked" for interaction. @@ -118,14 +118,14 @@ pub async fn start_register( pub async fn finish_register( Extension(app_state): Extension, - mut session: WritableSession, + session: Session, Json(reg): Json, ) -> Result { let (username, user_unique_id, reg_state): (String, Uuid, PasskeyRegistration) = session - .get("reg_state") + .get("reg_state")? .ok_or(WebauthnError::CorruptSession)?; //Corrupt Session - session.remove("reg_state"); + session.remove_value("reg_state"); let res = match app_state .webauthn @@ -185,7 +185,7 @@ pub async fn finish_register( pub async fn start_authentication( Extension(app_state): Extension, - mut session: WritableSession, + session: Session, Path(username): Path, ) -> Result { info!("Start Authentication"); @@ -193,7 +193,7 @@ pub async fn start_authentication( // some other process. // Remove any previous authentication that may have occured from the session. - session.remove("auth_state"); + session.remove_value("auth_state"); // Get the set of keys that the user possesses let users_guard = app_state.users.lock().await; @@ -241,14 +241,14 @@ pub async fn start_authentication( pub async fn finish_authentication( Extension(app_state): Extension, - mut session: WritableSession, + session: Session, Json(auth): Json, ) -> Result { let (user_unique_id, auth_state): (Uuid, PasskeyAuthentication) = session - .get("auth_state") + .get("auth_state")? .ok_or(WebauthnError::CorruptSession)?; - session.remove("auth_state"); + session.remove_value("auth_state"); let res = match app_state .webauthn diff --git a/tutorial/server/axum/src/error.rs b/tutorial/server/axum/src/error.rs index c7f098c3..bd9f550f 100644 --- a/tutorial/server/axum/src/error.rs +++ b/tutorial/server/axum/src/error.rs @@ -14,6 +14,8 @@ pub enum WebauthnError { UserNotFound, #[error("User Has No Credentials")] UserHasNoCredentials, + #[error("Deserialising Session failed: {0}")] + InvalidSessionState(#[from] tower_sessions::session::Error), } impl IntoResponse for WebauthnError { fn into_response(self) -> Response { @@ -22,6 +24,7 @@ impl IntoResponse for WebauthnError { WebauthnError::UserNotFound => "User Not Found", WebauthnError::Unknown => "Unknown Error", WebauthnError::UserHasNoCredentials => "User Has No Credentials", + WebauthnError::InvalidSessionState(_) => "Deserialising Session failed", }; // its often easiest to implement `IntoResponse` by calling other implementations diff --git a/tutorial/server/axum/src/main.rs b/tutorial/server/axum/src/main.rs index 0ae3af1e..08c6ea8d 100644 --- a/tutorial/server/axum/src/main.rs +++ b/tutorial/server/axum/src/main.rs @@ -1,7 +1,14 @@ -use axum::{extract::Extension, routing::post, Router}; -use axum_extra::routing::SpaRouter; -use axum_sessions::{async_session::MemoryStore, SameSite, SessionLayer}; +use axum::{ + error_handling::HandleErrorLayer, extract::Extension, http::StatusCode, routing::post, + BoxError, Router, +}; use std::net::SocketAddr; +use tower::ServiceBuilder; +use tower_sessions::{ + cookie::{time::Duration, SameSite}, + Expiry, MemoryStore, SessionManagerLayer, +}; + mod error; /* * Webauthn RS server side tutorial. @@ -12,8 +19,6 @@ mod error; use crate::auth::{finish_authentication, finish_register, start_authentication, start_register}; use crate::startup::AppState; -use rand::prelude::*; - #[macro_use] extern crate tracing; @@ -37,13 +42,18 @@ async fn main() { // Create the app let app_state = AppState::new(); - //Configure cookie based sessions - let store = MemoryStore::new(); - let secret = thread_rng().gen::<[u8; 128]>(); // MUST be at least 64 bytes! - let session_layer = SessionLayer::new(store, &secret) - .with_cookie_name("webauthnrs") - .with_same_site_policy(SameSite::Lax) - .with_secure(false); // TODO: change this to true when running on an HTTPS/production server instead of locally + let session_store = MemoryStore::default(); + let session_service = ServiceBuilder::new() + .layer(HandleErrorLayer::new(|_: BoxError| async { + StatusCode::BAD_REQUEST + })) + .layer( + SessionManagerLayer::new(session_store) + .with_name("webauthnrs") + .with_same_site(SameSite::Lax) + .with_secure(false) // TODO: change this to true when running on an HTTPS/production server instead of locally + .with_expiry(Expiry::OnInactivity(Duration::seconds(360))), + ); // build our application with a route let app = Router::new() @@ -52,17 +62,18 @@ async fn main() { .route("/login_start/:username", post(start_authentication)) .route("/login_finish", post(finish_authentication)) .layer(Extension(app_state)) - .layer(session_layer); + .layer(session_service); #[cfg(feature = "wasm")] - let app = Router::new() - .merge(app) - .merge(SpaRouter::new("/assets", "assets").index_file("wasm_index.html")); + let app = Router::new().merge(app).nest_service( + "/assets", + tower_http::services::ServeDir::new("assets/wasm"), + ); #[cfg(feature = "javascript")] let app = Router::new() .merge(app) - .merge(SpaRouter::new("/assets", "assets").index_file("js_index.html")); + .nest_service("/assets", tower_http::services::ServeDir::new("assets/js")); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server`