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

uploaded example using JWT and Rusqlite for authentication #805

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions JWT_Authentication_with_Actix-web_and_Rusqlite/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
132 changes: 132 additions & 0 deletions JWT_Authentication_with_Actix-web_and_Rusqlite/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<User>) -> 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<User>) -> 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::<JwtGen>(
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<String, jsonwebtoken::errors::Error> {
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)
}
Empty file.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
- [rayspace.dev](https://github.com/ryspc/rayspace.dev): Minimalist dev portfolio and blog implemented as a Rust-powered SPA, featuring GitHub OAuth, session management, static file serving, API endpoints, and SQLx integration.
- [Blog with markdown rendering](https://github.com/gemini-15/blog-engine): Blog example built with Actix Web, diesel (with Postgres) and r2d2 rendering articles in markdown with metadata and a front-end with React.
- [Rust, Angular, PostgreSQL and JWT Security](https://github.com/stav121/actix-angular-project-template): Boilerplate project that implements an Angular + Actix Web application with login and registration pages, that is pre-dockerized.
- [Actix-web with chacha20poly1305 encryption](https://github.com/Paxton21/chacha20poly1305-actix-web): A basic and swiftly thrown together example of having encrypted communications using chacha20poly1305

## Paid Resources

Expand Down
Loading