Sitemap

Warp | 17 , Auth Jwt

8 min readAug 18, 2026

--

Let’s implement JSON Web Token (JWT) authentication in Warp.

(Youtube video)

1

In the Cargo.toml file, we need to add the following dependencies:

[dependencies]
tokio = { version = "1", features = ["full"] }
warp = { version = "0.4", features = ["server"] }

argon2 = "0.5.3"
jsonwebtoken = "9.3.1"

serde = {version = "1.0.228", features = ["derive"]}


sqlx = { version="0.8.6", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] }

We add actix-web as the backend server, argon2 for hashing and verifying passwords, jsonwebtoken for encoding and decoding tokens, serde for JSON serialization and deserialization, and sqlx for connecting to a PostgreSQL database.

2

Open a terminal, and run the following commands:

psql

This opens the PostgreSQL shell.

CREATE DATABASE auth;

This creates a database named auth in PostgreSQL.

/c auth;

To connect to a database named auth in PostgreSQL.

CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255),
password VARCHAR(255)
);

This creates a table named users with three fields.

3

in main.rs file:

1

#[derive(Deserialize)]
struct UserReq {
username: String,
password: String
}
#[derive(FromRow)]
struct UserSql {
id: i32,
username: String,
password: String
}

We define two structs.

UserReq derives Deserialize to receive the username and password from incoming requests.

UserSql derives FromRow to map database rows containing username and password from PostgreSQL.

2

fn hash_password(password: &String) -> String {
let argon2 = Argon2::default();
let salt = SaltString::generate(&mut OsRng);
let hashed_password = argon2.hash_password(password.as_bytes(), &salt).unwrap().to_string();
hashed_password
}

We define a function called hash_password that takes a password as input and returns a hashed string.
Inside the function, we create a default Argon2 instance and generate a salt.
We then use Argon2 to hash the password with the salt and return the resulting hash.

3

fn verify_password(password: &String, hashed_password: &String) -> Result<(), password_hash::Error>{
let argon2 = Argon2::default();
let parsed_hash = PasswordHash::new(&hashed_password).unwrap();
argon2.verify_password(password.as_bytes(), &parsed_hash)
}

We define a function called verify_password that takes two parameters: password and hashed_password. Its return type is Result.
Inside the function, we create a default Argon2 instance and parse the hashed_password string into PHC format using PasswordHash::new.
We then call verify_password on the Argon2 instance to compare the password with the hash.
If the password is correct, the function returns Ok(()); if it is incorrect, it returns an Err containing a password_hash::Error.

4

#[derive(Deserialize, Serialize)]
struct Claims {
exp: usize,
sub: String
}

We define a struct called Claims for generating JSON Web Tokens.
It contains two fields: exp, which specifies the token’s expiration time, and sub, which holds user-specific information.
The struct derives Deserialize and Serialize to allow encoding and decoding of JSON data.

Get Mike Code’s stories in your inbox

Join Medium for free to get updates from this writer.

5

fn generate_token(username: String) -> String {
let claims = Claims {sub: username, exp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize + 60 * 60 * 24 };
let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_bytes())).unwrap();
token
}

We define a function to generate a JWT that takes a username as a parameter and returns a String.
Inside the function, we create a Claims instance where sub is set to the unique username and exp is set to 24 hours from the current time.
We then call encode from the jsonwebtoken crate, passing a default Header, a reference to the Claims instance, and an EncodingKey derived from the secret.

static SECRET : &str = "secret";

SECRET is the global variable we defined to store the JWT secret key.

6

fn verify_token(token: &str) -> Result<String, String> {
match decode(token, &DecodingKey::from_secret(SECRET.as_bytes()), &Validation::default()) {
Err(e) => {Err(e.to_string())},
Ok(token_data) => {
let claims : Claims = token_data.claims;
Ok(claims.sub)
}
}
}

We define a function called verify_token to validate a JWT, which returns a Result.
Inside the function, we call decode from the jsonwebtoken crate. The first argument is the token, the second is a DecodingKey created from the same secret used for encoding, and the third is a default Validation.
The decode function returns a Result, which we handle using a match expression.
If decoding fails, the function returns an Err containing the error message. If the token is valid, it returns Ok containing the token data. We extract the claims from this data and return Ok(claims.sub).

7

async fn db() -> PgPool {
let pool = sqlx::postgres::PgPool::connect("postgres://mike:123456@localhost:5432/auth").await.unwrap();
pool
}

We define a db function to connect to the PostgreSQL database.

8

async fn register_handler (user_req: UserReq, pool: PgPool) -> Result<warp::reply::WithStatus<String> , warp::Rejection> {
if user_req.username.trim() == "" || user_req.password == "" {
Ok(warp::reply::with_status("Username or Password must not be empty!".to_string(), warp::http::StatusCode::BAD_REQUEST))
} else {
let rows: Vec<UserSql> = sqlx::query_as("SELECT * FROM users WHERE username = $1").bind(&user_req.username).fetch_all(&pool).await.unwrap();

if rows.len() == 0 {
let hashed_password = hash_password(&user_req.password);

sqlx::query("INSERT INTO users (username, password) VALUES ($1, $2)").bind(&user_req.username).bind(&hashed_password).execute(&pool).await.unwrap();

Ok(warp::reply::with_status("register successful".to_string(), warp::http::StatusCode::OK))
} else {
Ok(warp::reply::with_status("Username is already taken!".to_string(), warp::http::StatusCode::BAD_REQUEST))
}
}
}

We define a register_handler to register a new user.
The handler takes two parameters: one for extracting the username and password from the request, and another for accessing the shared database pool.
First, it validates that both the username and password are not empty.
If they are valid, the handler checks whether the username is already registered in the database. If not, it hashes the password using the hash_password function and inserts the username and hashed password into the database.

9

async fn login_handler (user_req: UserReq, pool: PgPool) -> Result<impl warp::Reply , warp::Rejection> {
if user_req.username.trim() == "" || user_req.password == "" {
Ok(warp::reply::with_status("Username or Password must not be empty!".to_string(), warp::http::StatusCode::BAD_REQUEST))
} else {
let rows: Vec<UserSql> = sqlx::query_as("SELECT * FROM users WHERE username = $1").bind(&user_req.username).fetch_all(&pool).await.unwrap();
if rows.len() == 0 {
Ok(warp::reply::with_status("Username is not Registered!".to_string(), warp::http::StatusCode::BAD_REQUEST))
} else {
match verify_password(&user_req.password, &rows[0].password) {
Ok(_) => {
let token = generate_token(rows[0].username.clone());
Ok(warp::reply::with_status(token, warp::http::StatusCode::OK))
},
Err(_) => Ok(warp::reply::with_status("Password is Uncorrect!".to_string(), warp::http::StatusCode::UNAUTHORIZED))
}
}
}
}

We define a login_handler to authenticate users.
The handler takes two parameters: one for extracting the username and password from the request, and another for accessing the shared database pool.
It first validates that both the username and password are not empty.
Next, it checks whether the username is registered in the database. If it exists, the handler uses verify_password to compare the provided password with the stored hash.
If the password is correct, it generates a JWT token for the username using generate_token.

10

#[derive(Debug)]
struct AuthErr ;

impl warp::reject::Reject for AuthErr {}

We define a struct to represent authentication errors and implement the warp::reject::Reject trait for it.

11

async fn err_handler (err: warp::Rejection) -> Result<warp::reply::WithStatus<String> , warp::Rejection> {
if err.find::<AuthErr>().is_some() {
Ok(warp::reply::with_status("Unauthorized".to_string(), warp::http::StatusCode::UNAUTHORIZED))
}else {
Ok(warp::reply::with_status("something went wrong".to_string(), warp::http::StatusCode::INTERNAL_SERVER_ERROR))
}
}

We define an error handler that checks for AuthErr. If the error matches, the handler returns an “Unauthorized” response.

12

fn auth_middleware() -> impl Filter<Extract = (String,), Error = warp::Rejection> + Copy {

warp::header::optional("authorization").and_then(move |header: Option<String>| async {
match header {
None => Err(warp::reject::custom(AuthErr{})),
Some(header_value) => {
if header_value.starts_with("Bearer ") {
let token = header_value.split(" ").collect::<Vec<&str>>()[1];
match verify_token(token) {
Ok(sub) => {
Ok::<String, warp::Rejection>(sub)
},
Err(_e) => Err(warp::reject::custom(AuthErr{}) )
}
} else {
Err(warp::reject::custom(AuthErr{}))
}
}
}
})
}

We define middleware to verify the user.
It uses warp::header::optional to retrieve the Authorization header.
If the header exists and starts with Bearer , the token is extracted and passed to the verify_token function.
If the token is valid, the middleware returns the sub value from the token.

13

async fn protect_handler (sub: String) -> Result<warp::reply::WithStatus<String> , warp::Rejection> {
Ok(warp::reply::with_status(format!("hello {}", sub), warp::http::StatusCode::OK))
}

We define a protected route handler that receives the sub from the middleware when the user is authorized.

14

async fn app() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
let pool = db().await;
let db_filter = warp::any().map(move || pool.clone());
let root = warp::path!().map(|| "Home!");

let login = warp::post().and(warp::path!("login")).and(warp::body::json()).and(db_filter.clone()).and_then(login_handler);
let register = warp::path("register").and(warp::post()).and(warp::body::json()).and(db_filter.clone()).and_then(register_handler);



let protect = warp::get().and(warp::path!("protect")).and(auth_middleware()).and_then(protect_handler).recover(err_handler);

root
.or(login)
.or(register)
.or(protect)
}

We define an app function to create a Warp router and provide the database pool as shared state.
We set up four routes: a root route, and three additional routes (login, register, and protect).
The authentication middleware is applied only to the protect route.

15

#[tokio::main]
async fn main() {
let app = app().await;

warp::serve(app)
.run(([0,0,0,0], 3000))
.await;
}

Finally, we start the Warp server in the main function.

Full code:

use std::time::{SystemTime, UNIX_EPOCH};

use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier, password_hash::{self, SaltString, rand_core::OsRng}};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
use serde::{Deserialize, Serialize};
use sqlx::{PgPool, prelude::FromRow};
use warp::Filter;

static SECRET: &str = "secret";

//
#[tokio::main]
async fn main() {
let app = app().await;

warp::serve(app)
.run(([0,0,0,0], 3000))
.await;
}
//
async fn app() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
let pool = db().await;
let db_filter = warp::any().map(move || pool.clone());
let root = warp::path!().map(|| "Home!");

let login = warp::post().and(warp::path!("login")).and(warp::body::json()).and(db_filter.clone()).and_then(login_handler);
let register = warp::path("register").and(warp::post()).and(warp::body::json()).and(db_filter.clone()).and_then(register_handler);



let protect = warp::get().and(warp::path!("protect")).and(auth_middleware()).and_then(protect_handler).recover(err_handler);

root
.or(login)
.or(register)
.or(protect)
}
//
async fn login_handler (user_req: UserReq, pool: PgPool) -> Result<impl warp::Reply , warp::Rejection> {
if user_req.username.trim() == "" || user_req.password == "" {
Ok(warp::reply::with_status("Username or Password must not be empty!".to_string(), warp::http::StatusCode::BAD_REQUEST))
} else {
let rows: Vec<UserSql> = sqlx::query_as("SELECT * FROM users WHERE username = $1").bind(&user_req.username).fetch_all(&pool).await.unwrap();
if rows.len() == 0 {
Ok(warp::reply::with_status("Username is not Registered!".to_string(), warp::http::StatusCode::BAD_REQUEST))
} else {
match verify_password(&user_req.password, &rows[0].password) {
Ok(_) => {
let token = generate_token(rows[0].username.clone());
Ok(warp::reply::with_status(token, warp::http::StatusCode::OK))
},
Err(_) => Ok(warp::reply::with_status("Password is Uncorrect!".to_string(), warp::http::StatusCode::UNAUTHORIZED))
}
}
}
}
//
async fn register_handler (user_req: UserReq, pool: PgPool) -> Result<warp::reply::WithStatus<String> , warp::Rejection> {
if user_req.username.trim() == "" || user_req.password == "" {
Ok(warp::reply::with_status("Username or Password must not be empty!".to_string(), warp::http::StatusCode::BAD_REQUEST))
} else {
let rows: Vec<UserSql> = sqlx::query_as("SELECT * FROM users WHERE username = $1").bind(&user_req.username).fetch_all(&pool).await.unwrap();

if rows.len() == 0 {
let hashed_password = hash_password(&user_req.password);

sqlx::query("INSERT INTO users (username, password) VALUES ($1, $2)").bind(&user_req.username).bind(&hashed_password).execute(&pool).await.unwrap();

Ok(warp::reply::with_status("register successful".to_string(), warp::http::StatusCode::OK))
} else {
Ok(warp::reply::with_status("Username is already taken!".to_string(), warp::http::StatusCode::BAD_REQUEST))
}
}
}

//
async fn protect_handler (sub: String) -> Result<warp::reply::WithStatus<String> , warp::Rejection> {
Ok(warp::reply::with_status(format!("hello {}", sub), warp::http::StatusCode::OK))
}

//
fn auth_middleware() -> impl Filter<Extract = (String,), Error = warp::Rejection> + Copy {

warp::header::optional("authorization").and_then(move |header: Option<String>| async {
match header {
None => Err(warp::reject::custom(AuthErr{})),
Some(header_value) => {
if header_value.starts_with("Bearer ") {
let token = header_value.split(" ").collect::<Vec<&str>>()[1];
match verify_token(token) {
Ok(sub) => {
Ok::<String, warp::Rejection>(sub)
},
Err(_e) => Err(warp::reject::custom(AuthErr{}) )
}
} else {
Err(warp::reject::custom(AuthErr{}))
}
}
}
})
}
//
async fn err_handler (err: warp::Rejection) -> Result<warp::reply::WithStatus<String> , warp::Rejection> {
if err.find::<AuthErr>().is_some() {
Ok(warp::reply::with_status("Unauthorizad".to_string(), warp::http::StatusCode::INTERNAL_SERVER_ERROR))
}else {
Ok(warp::reply::with_status("something went wrong".to_string(), warp::http::StatusCode::INTERNAL_SERVER_ERROR))
}
}

//
#[derive(Deserialize)]
struct UserReq {
username: String,
password: String
}
//
#[derive(FromRow)]
struct UserSql {

id: i32,
username: String,
password: String
}
//
fn hash_password(password: &String) -> String {
let argon2 = Argon2::default();
let salt = SaltString::generate(&mut OsRng);

let hashed_password = argon2.hash_password(password.as_bytes(), &salt).unwrap().to_string();

hashed_password
}
//
fn verify_password(password: &String, hashed_password: &String) -> Result<(), password_hash::Error>{
let argon2 = Argon2::default();

let parsed_hash = PasswordHash::new(&hashed_password).unwrap();

argon2.verify_password(password.as_bytes(), &parsed_hash)
}
//
#[derive(Deserialize, Serialize)]
struct Claims {
exp: usize,
sub: String
}
//
fn generate_token(username: String) -> String {
let claims = Claims {sub: username, exp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize + 60 * 60 * 24 };

let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_bytes())).unwrap();
token
}
//
fn verify_token(token: &str) -> Result<String, String> {
match decode(token, &DecodingKey::from_secret(SECRET.as_bytes()), &Validation::default()) {
Err(e) => {Err(e.to_string())},
Ok(token_data) => {
let claims : Claims = token_data.claims;
Ok(claims.sub)
}
}
}

//
async fn db() -> PgPool {
let pool = sqlx::postgres::PgPool::connect("postgres://mike:123456@localhost:5432/auth").await.unwrap();

pool
}

//
#[derive(Debug)]
struct AuthErr ;

impl warp::reject::Reject for AuthErr {

}

--

--