r-token 0.1.7

A simple and efficient token generation library for Rust, ideal for API authentication and session management.
Documentation

r-token

r-token is a small token authentication helper for Rust and actix-web.

It provides two token managers:

  • In-memory: RTokenManager stores tokens in memory with an expiration timestamp.
  • Redis/Valkey (optional): RTokenRedisManager stores tokens in Redis with TTL.

For actix-web, r-token follows a “parameter-as-authentication” style: add RUser to handler parameters, and the request is authenticated automatically via the Actix extractor mechanism.

Features

  • Zero boilerplate: no custom middleware required for basic header auth.
  • Extractor-first: declaring RUser protects the route.
  • Thread-safe, shared state: RTokenManager is Clone and shares an in-memory store.
  • TTL support:
    • In-memory: tokens expire based on a per-login TTL (seconds).
    • Redis/Valkey: expiration is enforced by Redis TTL (seconds).
  • Redis/Valkey backend (optional): RTokenRedisManager stores user_id by token key.

Security notes

  • This library implements bearer-token authentication. Always use HTTPS in production.
  • Token strings grant access. Treat them like passwords: do not log them, do not store them in plaintext client storage without careful threat modeling.
  • The Redis backend stores user_id as the Redis value. If you need stronger protection against Redis data disclosure, consider storing a hashed token (not currently implemented by this crate).

Status

This project is in active development. Review the source code and tests before adopting it in security-sensitive environments.

This project has not yet released version 1.0.0 and is not recommended for use in any production-level projects. The current state should be considered experimental and potentially unstable.

Please note that this project has not been officially released. Both the documentation on docs.rs and this README file may not always reflect the latest changes in the source code. Always refer to the actual source code for the most accurate information. This documentation inconsistency will be resolved upon official release.

Installation

Add r-token to your Cargo.toml:

[dependencies]
r-token = "0.1.5"

Feature flags

r-token uses Cargo features to keep dependencies optional:

  • actix (default): enables the RUser extractor and actix-web integration.
  • redis: enables Redis/Valkey support via the redis crate.
  • redis-actix: convenience feature = redis + actix.
  • rbac: enables role-based access control (RBAC) support.

Examples:

[dependencies]
r-token = { version = "0.1.5", default-features = false }
[dependencies]
r-token = { version = "0.1.5", features = ["redis-actix"] }
[dependencies]
r-token = { version = "0.1.5", features = ["rbac"] }
[dependencies]
r-token = { version = "0.1.5", features = ["redis-actix", "rbac"] }

Authorization header

The RUser extractor (and the Redis example server) reads the token from Authorization and supports:

Authorization: <token>
Authorization: Bearer <token>

API overview

Core types:

  • RTokenManager (always available): issues and revokes tokens in memory.
  • RTokenError (always available): error type used by in-memory manager.

Actix integration (requires actix, enabled by default):

  • RUser: actix_web::FromRequest extractor that validates Authorization.

Redis backend (requires redis):

  • RTokenRedisManager: issues, validates, and revokes tokens backed by Redis/Valkey.

RBAC support (requires rbac):

  • RTokenManager::login_with_roles(): issues a token with associated roles.
  • RTokenManager::set_roles(): updates roles for an existing token.
  • RTokenManager::get_roles(): retrieves roles for a token.
  • RUser.roles: vector of roles associated with the authenticated user.
  • RUser::has_role(): checks if the user has a specific role.
  • RTokenRedisManager::login_with_roles(): issues a token with roles in Redis.
  • RTokenRedisManager::set_roles(): updates roles for a token in Redis.
  • RTokenRedisManager::get_roles(): retrieves roles for a token in Redis.
  • RTokenRedisManager::validate(): returns both user_id and roles when RBAC is enabled.

In-memory usage (actix-web)

1. Add endpoints

No manual header parsing is needed. Inject RUser into protected handlers.

use actix_web::{get, post, web, HttpResponse, Responder};
use r_token::{RTokenManager, RUser, RTokenError};

#[post("/login")]
async fn login(
    manager: web::Data<RTokenManager>,
    body: String,
) -> Result<impl Responder, RTokenError> {
    let token = manager.login(&body, 3600)?;
    Ok(HttpResponse::Ok().body(token))
}

#[get("/info")]
async fn info(user: RUser) -> impl Responder {
    format!("info: {}", user.id)
}

#[post("/logout")]
async fn logout(
    manager: web::Data<RTokenManager>,
    user: RUser,
) -> Result<impl Responder, RTokenError> {
    manager.logout(&user.token)?;
    Ok(HttpResponse::Ok().body("Logged out successfully"))
}

2. Register and Run

Initialize RTokenManager and register it with your Actix application.

use actix_web::{web, App, HttpServer};
use r_token::RTokenManager;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let manager = RTokenManager::new();

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(manager.clone()))
            .service(login)
            .service(info)
            .service(logout)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

RBAC usage (role-based access control)

When the rbac feature is enabled, you can assign roles to tokens and perform role-based authorization.

In-memory RBAC

use r_token::{RTokenManager, RUser, RTokenError};
use actix_web::{get, post, web, HttpResponse, Responder};

#[post("/login")]
async fn login(
    manager: web::Data<RTokenManager>,
    body: String,
) -> Result<impl Responder, RTokenError> {
    // Parse user_id and roles from request body
    let parts: Vec<&str> = body.split(':').collect();
    let user_id = parts[0];
    let roles: Vec<String> = parts[1..].iter().map(|s| s.to_string()).collect();

    let token = manager.login_with_roles(user_id, 3600, roles)?;
    Ok(HttpResponse::Ok().body(token))
}

#[get("/admin")]
async fn admin_only(user: RUser) -> impl Responder {
    if user.has_role("admin") {
        HttpResponse::Ok().body(format!("Welcome, admin {}", user.id))
    } else {
        HttpResponse::Forbidden().body("Access denied: admin role required")
    }
}

#[post("/promote")]
async fn promote(
    manager: web::Data<RTokenManager>,
    user: RUser,
) -> Result<impl Responder, RTokenError> {
    // Only admins can promote users
    if !user.has_role("admin") {
        return Ok(HttpResponse::Forbidden().body("Access denied"));
    }

    // Add 'moderator' role to the current user
    manager.set_roles(&user.token, vec!["admin".to_string(), "moderator".to_string()])?;
    Ok(HttpResponse::Ok().body("Promoted to moderator"))
}

#[get("/roles")]
async fn get_user_roles(user: RUser) -> impl Responder {
    HttpResponse::Ok().json(&user.roles)
}

Redis RBAC

use r_token::RTokenRedisManager;

#[tokio::main]
async fn main() -> Result<(), redis::RedisError> {
    let manager = RTokenRedisManager::connect("redis://127.0.0.1/", "r_token:token:")
        .await?;

    // Create token with roles
    let roles = vec!["admin".to_string(), "editor".to_string()];
    let token = manager.login_with_roles("alice", 3600, roles).await?;

    // Validate and get user info with roles
    let user_info = manager.validate(&token).await?;
    if let Some((user_id, retrieved_roles)) = user_info {
        println!("User: {}, Roles: {:?}", user_id, retrieved_roles);
    }

    // Update roles
    manager.set_roles(&token, vec!["admin".to_string()]).await?;

    // Get roles only
    let roles = manager.get_roles(&token).await?;
    println!("Roles: {:?}", roles);

    manager.logout(&token).await?;
    Ok(())
}

Behavioral details

In-memory manager:

  • RTokenManager::login(user_id, ttl_seconds) returns a UUID v4 token string.
  • Expiration is tracked by storing an absolute expiration timestamp (milliseconds since Unix epoch).
  • RTokenManager::logout(token) is idempotent: revoking a non-existent token is treated as success.

Actix extractor:

  • On success, RUser provides id and the raw token.
  • When RBAC is enabled, RUser also provides roles (a vector of role strings).
  • RUser::has_role(role) checks if the user has a specific role.
  • Failure modes:
    • 401 Unauthorized: missing token, invalid token, or expired token.
    • 500 Internal Server Error: token manager missing from app_data, or internal mutex poisoned.

Redis manager:

  • RTokenRedisManager::login(user_id, ttl_seconds) stores prefix + token as the key and user_id as the value, with Redis TTL set to ttl_seconds.
  • validate(token) returns Ok(None) when the key is absent (revoked or expired).
  • When RBAC is enabled, validate(token) returns Ok(Some(user_id)).
  • When RBAC is enabled, validate_with_roles(token) returns Ok(Some((user_id, roles))).
  • logout(token) deletes the key and is idempotent.

RBAC behavior:

  • Tokens can be created with roles via login_with_roles().
  • Roles can be updated on existing tokens via set_roles().
  • Roles can be retrieved via get_roles().
  • RUser.roles is always available (empty vector if no roles were assigned).
  • RUser::has_role() performs a case-sensitive string comparison.

Redis/Valkey usage

If you want token persistence and Redis-managed TTL expiration, enable redis (or redis-actix) and use RTokenRedisManager.

You also need a Tokio runtime in your application (do not rely on transitive dependencies):

[dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
use r_token::RTokenRedisManager;

#[tokio::main]
async fn main() -> Result<(), redis::RedisError> {
    let manager = RTokenRedisManager::connect("redis://127.0.0.1/", "r_token:token:")
        .await?;

    let token = manager.login("alice", 3600).await?;
    let user_id = manager.validate(&token).await?;
    assert_eq!(user_id.as_deref(), Some("alice"));

    manager.logout(&token).await?;
    Ok(())
}

Usage examples (curl)

Login

curl -X POST http://127.0.0.1:8080/login -d "alice"
# Response: 550e8400-e29b-41d4-a716-446655440000

Access Protected Resource

# Without Token -> 401 Unauthorized
curl http://127.0.0.1:8080/info

# With Token -> 200 OK
curl -H "Authorization: <token>" http://127.0.0.1:8080/info

Example servers in this repo

In-memory (actix-web)

cargo run --bin r-token

Redis/Valkey (actix-web)

Environment variables:

  • REDIS_URL (default: redis://127.0.0.1/)
  • R_TOKEN_PREFIX (default: r_token:token:)
REDIS_URL=redis://127.0.0.1/ cargo run --bin r-token-redis --features redis-actix

Roadmap

  • In-memory token management + extractor
  • Token expiration (TTL)
  • Redis/Valkey backend token storage (optional)
  • Role-based access control (RBAC)
  • Cookie support
  • In-memory token validation API (non-actix)
  • Redis actix-web extractor (parameter-as-authentication)
  • Configurable token sources (header/cookie name, priority)

License

MIT