Learning Rust - Security, TLS, and Auth
Episode 11 of 19

Learning Rust - Security, TLS, and Auth

This episode secures your Rust service: enabling HTTPS with TLS and certificates via rustls, implementing JWT and session authentication, as well as input protection, CORS, rate limiting, and security headers with tower-http.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A server that runs is only half the work — what separates a production service from a demo is security. Episode 11 secures your Rust application from three sides: transport encryption with TLS, identity verification with JWT and sessions, and HTTP-layer defenses such as CORS, rate limiting, and security headers.

This is not an optional topic. Every public API faces automated scanning, brute force, and common exploits. With the material in this episode, you build a security foundation that aligns with industry practices.

TLS and HTTPS with Rustls

The TLS Concept

TLS encrypts communication between client and server. For HTTPS, the server presents a certificate issued by a Certificate Authority (CA) such as Let's Encrypt. rustls is a pure-Rust TLS implementation that is secure and easy to use.

Creating a Local Certificate

For local development, create a self-signed certificate with openssl:

Generating a certificate
openssl req -x509 -newkey rsa:2048 \
  -keyout key.pem -out cert.pem \
  -days 365 -nodes -subj "/CN=localhost"

openssl req -x509 -newkey rsa:2048 creates a key and a self-signed certificate in one go. In production, use a certificate from a CA — in Kubernetes this is often done via cert-manager.

An HTTPS Server with Rustls

The pure-Rust TLS implementation is used through axum-server, which handles the certificate and the handshake:

TLS with axum
cat > src/main.rs <<'EOF'
use axum::{routing::get, Router};
 
#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "aman" }));
 
    axum_server::bind_rustls(
        std::net::SocketAddr::from(([127, 0, 0, 1], 8443)),
        axum_server::tls_rustls::RustlsConfig::from_pem_file(
            "cert.pem",
            "key.pem",
        )
        .await
        .unwrap(),
    )
    .serve(app)
    .await
    .unwrap();
}
EOF
cargo run

RustlsConfig::from_pem_file loads the certificate and key, then bind_rustls wraps the server with TLS. Test with curl -k https://localhost:8443 — the -k flag allows the self-signed certificate. Add the dependency with cargo add axum-server --features rustls. In production, TLS is often handled by an ingress or a reverse proxy such as nginx and traefik.

Authentication with JWT

JWT Structure

A JWT (JSON Web Token) carries signed claims. The server signs the token at login, the client stores it and sends it in the Authorization header, and the server verifies it without storing a session.

Signing and verifying a JWT
cat > src/main.rs <<'EOF'
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
 
#[derive(Serialize, Deserialize)]
struct Klaim {
    sub: String,
    exp: usize,
}
 
fn main() {
    let klaim = Klaim {
        sub: String::from("pengguna-1"),
        exp: 9999999999,
    };
 
    let token = encode(
        &Header::default(),
        &klaim,
        &EncodingKey::from_secret(b"rahasia"),
    )
    .unwrap();
    println!("token: {}", token);
 
    let data = decode::<Klaim>(
        &token,
        &DecodingKey::from_secret(b"rahasia"),
        &Validation::default(),
    )
    .unwrap();
 
    println!("sub: {}", data.claims.sub);
}
EOF
cargo run

encode signs the claims into a token; decode verifies the signature and reads the claims. exp is the expiration time. cargo add jsonwebtoken adds the library; in production, the secret is stored in the environment, not in the code.

Sessions and OAuth2

Sessions for Web Applications

For classic web applications, a session stores authentication state on the server side, while the client only holds the session id cookie. Crates such as axum-sessions or tower-sessions provide this, with storage in memory or a database.

OAuth2 for Third-Party Integration

OAuth2 lets users log in with an external provider (Google, GitHub). The authorization code flow: the application redirects the user to the provider, receives a code, then exchanges it for a token through the backend. The oauth2 crate handles the entire protocol:

The authorization code flow
use oauth2::{basic::BasicClient, AuthUrl, ClientId, TokenUrl};
 
let client = BasicClient::new(
    ClientId::new("id-klien".to_string()),
    None,
    AuthUrl::new("https://provider/authorize".to_string()).unwrap(),
    Some(TokenUrl::new("https://provider/token".to_string()).unwrap()),
);

BasicClient captures the provider configuration. The whole flow: redirect to auth_url, exchange the code at token_url, and validate the claims.

Input Protection, CORS, and Security Headers

CORS and Security Headers with tower-http

Cross-Origin Resource Sharing (CORS) controls who may call the API from a browser. tower-http provides ready-to-use layers:

CORS layer
let cors = CorsLayer::new()
    .allow_origin(Any)
    .allow_methods(Any)
    .allow_headers(Any);
 
let app = Router::new()
    .route("/", get(|| async { "api" }))
    .layer(cors);

CorsLayer sets the allowed origins, methods, and headers. For production, do not use Any for origins — restrict them to the domains you actually use.

Rate Limiting and Security Headers

Rate limiting caps the number of requests per client to hold back brute force:

Rate limit layer
let app = Router::new()
    .layer(RequestBodyLimitLayer::new(1024 * 1024))
    .route("/", get(|| async { "terbatas" }));

Do not forget the basic security headers: X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and Strict-Transport-Security once HTTPS is active. In Kubernetes, headers and rate limiting are often also handled by the ingress controller at the front layer.

Closing

Key takeaways:

  • TLS encrypts transport; rustls is a safe pure-Rust implementation.
  • Production certificates are issued by a CA; self-signed is enough for local development.
  • JWT: signed on the server, verified on every request, with claims like exp.
  • Sessions store state on the server; OAuth2 handles third-party login flows.
  • CORS controls browser access; restrict origins in production.
  • Rate limiting and security headers deter abuse from the HTTP layer on up.

In the next episode 12 we will discuss concurrency, async, and parallelism — the async/await model with the tokio runtime, task spawning and channels with tokio::sync, as well as thread safety through the Send and Sync traits and actor-like patterns. You will process many things concurrently and safely.

Learning Rust - Security, TLS, and Auth | Learning Rust