armature-framework 0.3.0

A modern, type-safe HTTP framework for Rust inspired by Angular and NestJS. Features dependency injection, decorators, middleware, authentication (JWT/OAuth2/SAML), validation, OpenAPI/Swagger, caching, job queues, and observability.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# Stateless Architecture

Armature is designed as a **completely stateless** framework, following RESTful and cloud-native best practices.

## Core Principles

### 1. No Server-Side Sessions

Armature does **not** implement or provide:
- Session storage mechanisms
- In-memory session management
- Session cookies
- Server-side user state persistence
- Session stores (Redis, memcached, etc.)

### 2. Stateless Authentication

All authentication is token-based and stateless:

```rust
// JWT tokens carry all user information
let token = jwt_manager.create_token(user_claims)?;

// Each request is independent
// Extract user from token on every request
let claims = jwt_manager.verify_token(&token)?;
```

**Key Points:**
- User identity is embedded in tokens (JWT)
- No server-side session lookup required
- Each request contains all necessary auth information
- Tokens can be validated without database lookups

### 3. Request Context Only

User information exists only for the duration of a single request:

```rust
#[controller("/api")]
struct UserController;

impl UserController {
    async fn get_profile(&self, req: HttpRequest) -> Result<Json<User>, Error> {
        // Extract user from JWT token in request
        let token = extract_bearer_token(&req)?;
        let claims = verify_token(token)?;

        // Use claims for this request only
        // No session storage
        Ok(Json(get_user_from_db(claims.user_id)?))
    }
}
```

## Authentication Patterns

### JWT-Based Authentication

```rust
use armature_jwt::{JwtManager, JwtConfig};

// Create token at login
let token = jwt_manager.create_token(UserClaims {
    user_id: user.id,
    email: user.email,
    roles: user.roles,
})?;

// Client stores token (localStorage, etc.)
// Client sends token with each request
// Authorization: Bearer <token>

// Server verifies token on each request
let claims = jwt_manager.verify_token(&token)?;
// Use claims.user_id, claims.roles, etc.
```

### OAuth2/OIDC Integration

OAuth2 flows are stateless using PKCE:

```rust
use armature_auth::providers::GoogleProvider;

// 1. Generate auth URL with PKCE
let (auth_url, pkce_verifier) = provider
    .authorization_url_with_pkce()
    .map_err(|e| Error::Internal(e.to_string()))?;

// 2. Store PKCE verifier client-side (NOT on server)
// Client handles the PKCE flow

// 3. Exchange code for token (stateless)
let token = provider.exchange_code_pkce(code, pkce_verifier).await?;

// 4. Create JWT from user info
let user_info = provider.get_user_info(&token).await?;
let jwt = jwt_manager.create_token(user_info)?;

// Return JWT to client
```

**Important:** CSRF/state tokens are handled client-side or embedded in redirect URLs, not stored on server.

### SAML 2.0

SAML integration is stateless:

```rust
use armature_auth::saml::SamlServiceProvider;

// 1. Generate authentication request
let authn_request = saml_provider.create_authn_request()?;

// 2. Redirect user with request embedded in URL
// No server-side state stored

// 3. Validate SAML response (stateless validation)
let assertion = saml_provider.validate_response(&saml_response)?;

// 4. Create JWT from assertion
let jwt = jwt_manager.create_token(UserClaims {
    user_id: assertion.name_id,
    email: assertion.attributes.get("email"),
    // ...
})?;

// Return JWT to client
```

## Why Stateless?

### Benefits

1. **Horizontal Scalability**
   - Any server can handle any request
   - No session affinity (sticky sessions) needed
   - Load balancing is trivial

2. **Cloud-Native**
   - Works seamlessly with containers
   - No shared state between instances
   - Perfect for Kubernetes, serverless, etc.

3. **Reliability**
   - No session store to fail
   - No session synchronization issues
   - Server restarts don't affect users

4. **Performance**
   - No session lookups
   - No database queries for auth
   - Token validation is cryptographic (fast)

5. **Security**
   - No session hijacking
   - No session fixation attacks
   - Token expiration is built-in

### Trade-offs

1. **Token Size**
   - JWTs can be larger than session IDs
   - Include only necessary claims

2. **Token Revocation**
   - Tokens are valid until expiry
   - Use short expiration times (15-60 minutes)
   - Implement token refresh flow
   - For immediate revocation, maintain token blacklist (separate concern)

3. **Client Responsibility**
   - Client must store and manage tokens
   - Client must handle token refresh

## Anti-Patterns to Avoid

### ❌ Don't Create Session Storage

```rust
// BAD - Don't do this
lazy_static! {
    static ref SESSIONS: Arc<Mutex<HashMap<String, UserSession>>> =
        Arc::new(Mutex::new(HashMap::new()));
}

// This breaks stateless architecture
fn store_session(session_id: String, user: User) {
    let mut sessions = SESSIONS.lock().unwrap();
    sessions.insert(session_id, UserSession { user });
}
```

### ❌ Don't Cache User Data Server-Side

```rust
// BAD - Don't do this
lazy_static! {
    static ref USER_CACHE: Arc<Mutex<HashMap<String, User>>> =
        Arc::new(Mutex::new(HashMap::new()));
}

// This creates state
fn cache_user(user_id: String, user: User) {
    let mut cache = USER_CACHE.lock().unwrap();
    cache.insert(user_id, user);
}
```

### ❌ Don't Store Request-Specific State

```rust
// BAD - Don't do this
static mut CURRENT_USER: Option<User> = None;

// This breaks concurrent requests
fn set_current_user(user: User) {
    unsafe {
        CURRENT_USER = Some(user);
    }
}
```

## Correct Patterns

### ✅ Extract User from Token Each Request

```rust
// GOOD - Stateless
async fn get_user(req: HttpRequest) -> Result<User, Error> {
    let token = extract_bearer_token(&req)?;
    let claims = jwt_manager.verify_token(token)?;

    // Optionally fetch from database
    // (database is external state, not server state)
    let user = database.find_user(&claims.user_id).await?;

    Ok(user)
}
```

### ✅ Use Guards for Auth

```rust
// GOOD - Validates on each request
use armature_framework::{Guard, GuardContext};

pub struct AuthenticationGuard;

#[async_trait]
impl Guard for AuthenticationGuard {
    async fn can_activate(&self, context: &GuardContext) -> Result<bool, Error> {
        let header = context.get_header("authorization")
            .ok_or_else(|| Error::Forbidden("Missing auth".to_string()))?;

        let token = extract_bearer_token(header)?;
        let _claims = jwt_manager.verify_token(token)?;

        // Token is valid
        Ok(true)
    }
}
```

### ✅ Use Middleware for Request Context

```rust
// GOOD - Attach user to request (not persistent)
pub struct AuthMiddleware {
    jwt_manager: JwtManager,
}

#[async_trait]
impl Middleware for AuthMiddleware {
    async fn handle(&self, mut req: HttpRequest, next: Next) -> Result<HttpResponse, Error> {
        if let Some(auth_header) = req.headers.get("authorization") {
            if let Ok(token) = extract_bearer_token(auth_header) {
                if let Ok(claims) = self.jwt_manager.verify_token(token) {
                    // Add to request headers (request-scoped only)
                    req.headers.insert("x-user-id".to_string(), claims.sub);
                }
            }
        }

        next(req).await
    }
}
```

## Token Refresh Pattern

For long-lived sessions without server-side state:

```rust
#[derive(Serialize)]
struct TokenPair {
    access_token: String,  // Short-lived (15-60 min)
    refresh_token: String, // Long-lived (7-30 days)
}

// Login: return both tokens
let access_token = jwt_manager.create_token(user_claims)?;
let refresh_token = jwt_manager.create_refresh_token(user_claims)?;

// When access token expires:
// Client calls /auth/refresh with refresh_token
// Server validates refresh_token
// Server issues new access_token
// No session lookup needed
```

## Rate Limiting (Stateless)

Even rate limiting can be stateless using distributed stores:

```rust
// Use external store (Redis, etc.) not in-memory
use redis::AsyncCommands;

async fn check_rate_limit(ip: &str) -> Result<bool, Error> {
    let mut conn = redis_client.get_async_connection().await?;
    let key = format!("rate:{}",  ip);

    let count: u32 = conn.get(&key).await.unwrap_or(0);

    if count > 100 {
        return Err(Error::TooManyRequests("Rate limit exceeded".into()));
    }

    conn.incr(&key, 1).await?;
    conn.expire(&key, 60).await?; // 60 seconds

    Ok(true)
}
```

**Note:** This uses external state (Redis), not server state. Any server instance can check the rate limit.

## WebSockets and SSE

Even real-time features remain stateless:

```rust
// WebSocket connections are ephemeral
// No user state persists beyond connection lifetime

async fn handle_websocket(req: HttpRequest) -> Result<(), Error> {
    // Authenticate via token in initial request
    let token = req.query_params.get("token")
        .ok_or_else(|| Error::Forbidden("Missing token".into()))?;

    let claims = jwt_manager.verify_token(token)?;

    // Connection is scoped to this handler
    // When connection closes, all state is gone

    websocket_upgrade(req, move |socket| async move {
        // Handle messages
        // User info from claims (not stored globally)
    }).await
}
```

## Deployment Considerations

### Multiple Instances

```
┌──────────┐     ┌──────────┐     ┌──────────┐
│ Server 1 │     │ Server 2 │     │ Server 3 │
│ (stateless)    │ (stateless)    │ (stateless)
└────┬─────┘     └────┬─────┘     └────┬─────┘
     │                │                │
     └────────────────┴────────────────┘
              ┌───────▼────────┐
              │  Load Balancer │
              └───────┬────────┘
              ┌───────▼────────┐
              │     Client      │
              │  (stores JWT)   │
              └─────────────────┘
```

Each server instance:
- Can handle any request
- Validates JWT independently
- No shared state needed
- No session synchronization

### Container/Kubernetes Friendly

```yaml
# Perfect for Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
  name: armature-app
spec:
  replicas: 10  # Scale freely
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        # No volume mounts for sessions
        # No sticky sessions needed
```

## Summary

Armature enforces stateless architecture by:

1. **No session framework** - Not provided, not supported
2. **JWT-based auth** - All user context in tokens
3. **Request-scoped data** - No persistence between requests
4. **External stores only** - Database, cache (Redis) are external, not in-server memory
5. **Cloud-native design** - Horizontal scaling without shared state

This design ensures your Armature application can:
- Scale horizontally with ease
- Deploy anywhere (containers, serverless, VMs)
- Handle millions of requests across multiple instances
- Recover from failures without user impact
- Maintain security without session vulnerabilities

**Remember:** If you need to "remember" something about a user, put it in the JWT or look it up from a database on each request. Never store it in server memory.