Back to Home

Development of an authorization service on Rust: tokens, encryption, Redis

The article is dedicated to the development of an authorization service on Rust, covering generation of JWT and opaque tokens, their verification using asymmetric encryption and storage in Redis. The material includes practical code examples and discussion of potential vulnerabilities.

How to build an authorization service on Rust: from basics to advanced techniques
Advertisement 728x90

Building a Secure Rust Auth Service: Tokens, Encryption, and Redis

Creating a robust authentication service in Rust demands a solid grasp of cryptography, token management, and smart abstractions. This article dives into hands-on experience, covering JWT and opaque token generation, validation, and Redis storage.

Token Architecture and Generation

The service uses two token types: access (JWT) for short-lived sessions and refresh (opaque) for seamless renewals without re-authentication.

JWT tokens leverage asymmetric encryption with the RS256 algorithm, requiring a private key for signing and a public key for verification. Opaque tokens are random base64 strings with no client-readable info, boosting security.

Google AdInline article slot

Key Generation Components

  • JwtTokenProvider: Trait for JWT generation using claims and a private key.
  • OpaqueTokenProvider: Trait for opaque tokens via random bytes.
  • Claims structure: Includes standard fields like sub (user ID), jti (token ID), iat (issued at), and exp (expiration).

Example JwtTokenProvider implementation:

pub trait IJwtTokenProvider {
    type Claims: Send + Sync;
    type Error;
    fn generate(&self, claims: &Self::Claims, pem: &str) -> Result<String, Self::Error>;
}

Token Verification and Validation

JWT verification uses the public key to block compromised private keys. The IJwtTokenValidator trait handles signature checks and token integrity.

Validation Highlights

  • PEM format: Keys stored in PEM files, supporting certificates and trust chains.
  • Encryption algorithms: Supports symmetric (e.g., HS256) and asymmetric (e.g., RS256), favoring the latter for better security.
  • Claims conversion: Domain claim models adapted for library compatibility, like DateTime to usize.

Verification code example:

Google AdInline article slot
pub trait IJwtTokenValidator {
    type Claims: Send + Sync;
    type Error;
    fn verify(&self, token: &str, pem: &str) -> Result<Self::Claims, Self::Error>;
}

Token Storage and Orchestration

TokenManager and KeyManager handle tokens and keys. TokenManager generates, verifies, and stores tokens in Redis using flexible abstractions.

RedisIO Implementation

RedisIO simplifies storage ops with setex, get, and delete methods, cutting boilerplate and improving readability.

Example:

Google AdInline article slot
impl<Storage> RedisIO<Storage>
where Storage: redis::AsyncCommands + Send + Sync
{
    pub async fn setex(&mut self, key: &str, data: &str, exp: u64) -> Result<(), redis::RedisError> {
        self.redis_storage.set_ex::<&str, String, (>(&key, data.to_string(), exp).await?;
        Ok(())
    }
}

TokenManager Features

Uses generics for providers and validators, enabling easy swaps. Key methods:

  • generate_pair: Creates access/refresh pair, stores in Redis with TTL.
  • verify_access: Validates access token existence and integrity.

Usage example:

pub async fn generate_pair(&mut self, claims: &Claims, pem: &str) -> Result<(String, String), TokenManagerError> {
    let access_token = self.access_provider.generate(&claims, &pem)?;
    let refresh_token = self.refresh_provider.generate();
    // Redis storage logic
    Ok((access_token, refresh_token))
}

Key Takeaways

  • RS256 asymmetric encryption for JWTs trumps symmetric methods in security.
  • Opaque tokens as random base64 strings resist forgery.
  • Traits enable easy testing and swaps.
  • Redis TTL storage manages sessions efficiently, easing DB load.
  • Strict private/public key separation prevents breaches.

Common Pitfalls and Fixes

Development uncovered issues:

  • Base64 bloat: 32 bytes expand to 43, risking storage glitches.
  • Key mishandling: Using private keys for verification is a no-go.
  • Type mismatches: DateTime to usize needs validation.

Solutions:

  • Test edge cases with varied data sizes.
  • Enforce strict key type/format checks.
  • Leverage battle-tested JWT/crypto libraries.

— Editorial Team

Advertisement 728x90

Read Next