To create a secure authentication system in Rust, we will utilize the actix-web framework for the web server and bcrypt for password hashing. We will also discuss token-based authentication using JSON Web Tokens (JWT) for session management. By the end of this tutorial, you will have a foundational understanding of how to implement secure authentication in your Rust applications.

Setting Up Your Rust Project

First, create a new Rust project using Cargo:

cargo new rust_auth_example
cd rust_auth_example

Next, add the necessary dependencies to your Cargo.toml file:

[dependencies]
actix-web = "4.0"
bcrypt = "0.12"
jsonwebtoken = "7.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

User Registration

Let's start with user registration, where we will hash the user's password before storing it. This is crucial for protecting user credentials.

User Model

Create a user model to represent the user data:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct User {
    username: String,
    password: String,
}

Registration Handler

Now, implement the registration handler that hashes the password and stores the user data:

use actix_web::{post, web, HttpResponse, Responder};
use bcrypt::{hash, DEFAULT_COST};

#[post("/register")]
async fn register(user: web::Json<User>) -> impl Responder {
    let hashed_password = hash(&user.password, DEFAULT_COST).unwrap();
    
    // Here you would typically save the user to a database
    // For this example, we will just return a success message
    HttpResponse::Created().json(format!("User {} registered successfully!", user.username))
}

Main Function

Now, set up the main function to run the Actix web server:

use actix_web::{App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(register)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

User Login and JWT Generation

Next, we will implement user login, where we will verify the password and generate a JWT for session management.

Login Handler

Implement the login handler that checks the password and issues a JWT:

use actix_web::{post, web, HttpResponse, Responder};
use bcrypt::verify;
use jsonwebtoken::{encode, Header, EncodingKey};
use std::time::{SystemTime, UNIX_EPOCH};

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

#[post("/login")]
async fn login(user: web::Json<User>) -> impl Responder {
    // Here you would typically retrieve the user from a database
    let stored_hashed_password = "$2b$12$..."; // Replace with actual hash

    if verify(&user.password, stored_hashed_password).unwrap() {
        let expiration = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() + 3600; // Token valid for 1 hour

        let claims = Claims {
            sub: user.username.clone(),
            exp: expiration as usize,
        };

        let token = encode(&Header::default(), &claims, &EncodingKey::from_secret("secret".as_ref())).unwrap();

        HttpResponse::Ok().json(token)
    } else {
        HttpResponse::Unauthorized().finish()
    }
}

Token Validation Middleware

To protect your routes, you can create a middleware to validate the JWT:

use actix_web::{dev::Service, web, Error, HttpRequest, HttpResponse};

async fn validate_token(req: HttpRequest) -> Result<HttpResponse, Error> {
    let token = req.headers().get("Authorization").unwrap().to_str().unwrap();
    
    // Validate token here
    // If valid, proceed; otherwise, return Unauthorized
    Ok(req.error_response(HttpResponse::Unauthorized()))
}

Summary of Best Practices

PracticeDescription
Password HashingAlways hash passwords using a strong algorithm (e.g., bcrypt).
Use JWTs for SessionsUse JWTs for stateless session management, ensuring they are signed and valid.
Secure Token StorageStore sensitive tokens securely and avoid exposing them to client-side scripts.
Validate User InputAlways validate and sanitize user input to prevent injection attacks.
Use HTTPSEnsure that your application uses HTTPS to protect data in transit.

Conclusion

Implementing secure authentication in Rust requires careful attention to detail, from hashing passwords to managing user sessions with JWTs. By following the practices outlined in this tutorial, you can build a robust authentication system that protects your users' data.

Learn more with useful resources: