diff --git a/JWT_Authentication_with_Actix-web_and_Rusqlite/Cargo.toml b/JWT_Authentication_with_Actix-web_and_Rusqlite/Cargo.toml new file mode 100644 index 00000000..8b63a460 --- /dev/null +++ b/JWT_Authentication_with_Actix-web_and_Rusqlite/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jwt_example" +version = "0.1.0" +edition = "2021" +author = "Paxton Smith" +# https://www.linkedin.com/in/paxton21/ +# https://github.com/Paxton21/actix-web-jwt-example + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "4.5.1" +jsonwebtoken = "9.3.0" +rusqlite = { version = "0.31.0", features = ["bundled"] } +serde = { version = "1.0.198", features = ["derive"]} +chrono = "0.4.38" \ No newline at end of file diff --git a/JWT_Authentication_with_Actix-web_and_Rusqlite/src/main.rs b/JWT_Authentication_with_Actix-web_and_Rusqlite/src/main.rs new file mode 100644 index 00000000..c9577970 --- /dev/null +++ b/JWT_Authentication_with_Actix-web_and_Rusqlite/src/main.rs @@ -0,0 +1,132 @@ +use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder}; +use chrono::{Duration, Utc}; +use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; +use rusqlite::{Connection, Result}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +struct User { + username: String, + password: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct JwtGen { + username: String, + exp: i64, +} + +const SEC_KEY: &[u8] = b"my_very_secret_key_def_not_known"; + +async fn register(user: web::Json) -> impl Responder { + if let Err(err) = db_reg(&user) { + return HttpResponse::InternalServerError().body(err.to_string()); + } + HttpResponse::Ok().body("User registered successfully") +} + +async fn login(user: web::Json) -> impl Responder { + if let Err(_) = authenticate_user(&user).await { + return HttpResponse::Unauthorized().body("Invalid username or password"); + } + + match generate_token(&user.username) { + Ok(token) => HttpResponse::Ok().body(token), + Err(_) => HttpResponse::InternalServerError().body("Internal Server Error"), + } +} + +async fn protected(req: HttpRequest) -> impl Responder { + if let Some(token) = req + .headers() + .get("jwt") + .and_then(|value| value.to_str().ok()) + { + if let Ok(token_data) = decode::( + token, + &DecodingKey::from_secret(SEC_KEY), + &Validation::new(Algorithm::HS256), + ) { + if token_data.claims.exp < Utc::now().timestamp() { + return HttpResponse::Unauthorized().body("Token expired"); + } + + return HttpResponse::Ok().body("Welcome to the protected route"); + } + } + + HttpResponse::Unauthorized().body("Missing or invalid JWT token in the 'jwt' header") +} + +async fn unprotected() -> impl Responder { + "Unprotected endpoint (does not require authentication)" +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| { + App::new() + .route("/register", web::post().to(register)) + .route("/login", web::post().to(login)) + .route("/protected", web::get().to(protected)) + .route("/unprotected", web::get().to(unprotected)) + }) + .bind("127.0.0.1:8080")? + .run() + .await +} + +fn db_reg(user: &User) -> Result<()> { + let conn = Connection::open("users.db")?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL + )", + [], + )?; + + conn.execute( + "INSERT INTO users (username, password) VALUES (?1, ?2)", + &[&user.username, &user.password], + )?; + + Ok(()) +} + +async fn authenticate_user(user: &User) -> Result<(), ()> { + let conn = Connection::open("users.db").map_err(|_| ())?; + let mut stmt = conn + .prepare("SELECT * FROM users WHERE username = ?1") + .map_err(|_| ())?; + let mut rows = stmt.query(&[&user.username]).map_err(|_| ())?; + + if let Some(row) = rows.next().map_err(|_| ())? { + let stored_password: String = row.get(2).map_err(|_| ())?; + if stored_password != user.password { + return Err(()); + } + } else { + return Err(()); + } + Ok(()) +} + +fn generate_token(username: &str) -> Result { + let exp = Utc::now() + Duration::hours(2); // Set expiration time to 2 hours from now + + let claims = JwtGen { + username: username.to_owned(), + exp: exp.timestamp(), + }; + + let token = encode( + &Header::default(), + &claims, + &EncodingKey::from_secret(SEC_KEY), + )?; + + Ok(token) +} \ No newline at end of file diff --git a/JWT_Authentication_with_Actix-web_and_Rusqlite/users.db b/JWT_Authentication_with_Actix-web_and_Rusqlite/users.db new file mode 100644 index 00000000..e69de29b