Move lib to root dir
This commit is contained in:
parent
88b21fc08d
commit
0d26e9326a
13 changed files with 2 additions and 1 deletions
255
README.md
Normal file
255
README.md
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
# SF Auth Middleware for Axum
|
||||
|
||||
Authentication middleware for Axum applications using the SnazzyFellas authentication service with tower_sessions for session management.
|
||||
|
||||
## Features
|
||||
|
||||
- **Middleware**: Automatically redirect unauthenticated users to the SF auth endpoint
|
||||
- **Extractor**: Type-safe access to authenticated user information via the `SfUser` extractor
|
||||
- **Callback Handler**: Ready-to-use route handler for authentication callbacks
|
||||
- **Session Integration**: Seamless integration with tower-sessions
|
||||
- **Fail-Closed Security**: Validation failures result in denied access, not automatic approval
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
sf-auth-middleware-axum = "0.1"
|
||||
axum = "0.8"
|
||||
tower-sessions = "0.15"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```rust
|
||||
use axum::{routing::get, Router, middleware};
|
||||
use sf_auth_middleware_axum::{SfAuthConfig, sf_auth_middleware, auth_callback, SfUser};
|
||||
use tower_sessions::{MemoryStore, SessionManagerLayer};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Configure the authentication middleware
|
||||
let config = SfAuthConfig::new("https://myapp.com/dashboard");
|
||||
|
||||
// Set up session store
|
||||
let session_store = MemoryStore::default();
|
||||
let session_layer = SessionManagerLayer::new(session_store);
|
||||
|
||||
// Build your application
|
||||
let app = Router::new()
|
||||
// Public callback route (no auth required)
|
||||
.route("/auth/callback", get(auth_callback))
|
||||
// Protected routes
|
||||
.route("/dashboard", get(dashboard))
|
||||
.layer(middleware::from_fn(move |session, req, next| {
|
||||
sf_auth_middleware(config.clone(), session, req, next)
|
||||
}))
|
||||
// Add session layer
|
||||
.layer(session_layer);
|
||||
|
||||
// Run the server
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
async fn dashboard(user: SfUser) -> String {
|
||||
format!("Hello, {}! Your ID: {}", user.username(), user.user_id())
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Protection**: Apply the middleware to routes that require authentication
|
||||
2. **Session Check**: The middleware checks for `sf_username` and `sf_user_id` in the session
|
||||
3. **Redirect**: If not authenticated, redirects to the SF authentication endpoint:
|
||||
```
|
||||
https://snazzyfellas.com/api/redirect/authenticate?redirect_uri={your_configured_uri}
|
||||
```
|
||||
4. **Callback**: The SF server redirects back to `/auth/callback` with credentials (`user_id`, `username`, `key`)
|
||||
5. **Validation**: The callback handler validates credentials with the SF server:
|
||||
```
|
||||
POST https://snazzyfellas.com/api/redirect/validate
|
||||
Body: { "user_id": "...", "key": "..." }
|
||||
Response: { "valid": true, "user_id": "..." }
|
||||
```
|
||||
6. **Session Setup**: On successful validation, sets `sf_username` and `sf_user_id` in the session
|
||||
7. **Access Granted**: Use the `SfUser` extractor in handlers to access authenticated user data
|
||||
|
||||
## Architecture
|
||||
|
||||
### Configuration (`SfAuthConfig`)
|
||||
|
||||
Configure the redirect URI where users should land after authentication:
|
||||
|
||||
```rust
|
||||
let config = SfAuthConfig::new("https://myapp.com/dashboard");
|
||||
```
|
||||
|
||||
### Middleware (`sf_auth_middleware`)
|
||||
|
||||
The middleware function checks authentication and redirects unauthenticated users:
|
||||
|
||||
```rust
|
||||
use axum::middleware;
|
||||
|
||||
.layer(middleware::from_fn(move |session, req, next| {
|
||||
sf_auth_middleware(config.clone(), session, req, next)
|
||||
}))
|
||||
```
|
||||
|
||||
### Callback Route (`auth_callback`)
|
||||
|
||||
Mount this handler at `/auth/callback` to receive authentication callbacks:
|
||||
|
||||
```rust
|
||||
.route("/auth/callback", get(auth_callback))
|
||||
```
|
||||
|
||||
This route:
|
||||
- Receives `user_id`, `username`, and `key` as query parameters
|
||||
- Validates credentials with the SF server
|
||||
- Sets session values on successful validation
|
||||
- Returns error on validation failure (fail-closed)
|
||||
|
||||
### Extractor (`SfUser`)
|
||||
|
||||
Use the `SfUser` extractor in your handlers to access authenticated user data:
|
||||
|
||||
```rust
|
||||
async fn protected_handler(user: SfUser) -> String {
|
||||
format!("Username: {}, ID: {}", user.username(), user.user_id())
|
||||
}
|
||||
```
|
||||
|
||||
The extractor provides:
|
||||
- `user.username()` - The authenticated user's username
|
||||
- `user.user_id()` - The authenticated user's ID
|
||||
|
||||
If the session doesn't contain valid credentials, the extractor returns a `401 Unauthorized` error.
|
||||
|
||||
## Session Keys
|
||||
|
||||
The middleware uses fixed session keys for consistency:
|
||||
- `sf_username` - Stores the authenticated user's username
|
||||
- `sf_user_id` - Stores the authenticated user's ID
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library uses a fail-closed security model:
|
||||
|
||||
- **Network Errors**: If validation API calls fail, authentication is denied
|
||||
- **Invalid Response**: Malformed responses from the validation endpoint result in denied access
|
||||
- **Validation Failure**: If the SF server returns `valid: false`, session is not set
|
||||
- **User ID Mismatch**: If the returned user_id doesn't match the request, authentication is denied
|
||||
|
||||
All errors implement Axum's `IntoResponse` trait for automatic HTTP error responses.
|
||||
|
||||
## Session Store
|
||||
|
||||
This library works with any tower-sessions store. Common options:
|
||||
|
||||
### Memory Store (Development)
|
||||
```rust
|
||||
use tower_sessions::MemoryStore;
|
||||
let session_store = MemoryStore::default();
|
||||
```
|
||||
|
||||
### Redis Store (Production)
|
||||
```rust
|
||||
use tower_sessions_redis_store::RedisStore;
|
||||
let pool = deadpool_redis::Pool::new(...);
|
||||
let session_store = RedisStore::new(pool);
|
||||
```
|
||||
|
||||
### PostgreSQL Store (Production)
|
||||
```rust
|
||||
use tower_sessions_sqlx_store::PostgresStore;
|
||||
let pool = sqlx::PgPool::connect("...").await?;
|
||||
let session_store = PostgresStore::new(pool);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Run the included example:
|
||||
|
||||
```bash
|
||||
cargo run --example basic
|
||||
```
|
||||
|
||||
Then visit:
|
||||
- `http://localhost:3000/` - Public home page
|
||||
- `http://localhost:3000/dashboard` - Protected page (will redirect to SF auth)
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SfAuthConfig`
|
||||
|
||||
```rust
|
||||
pub struct SfAuthConfig { /* ... */ }
|
||||
|
||||
impl SfAuthConfig {
|
||||
pub fn new(redirect_uri: impl Into<String>) -> Self
|
||||
pub fn redirect_uri(&self) -> &str
|
||||
}
|
||||
```
|
||||
|
||||
### `SfUser`
|
||||
|
||||
```rust
|
||||
pub struct SfUser { /* ... */ }
|
||||
|
||||
impl SfUser {
|
||||
pub fn username(&self) -> &str
|
||||
pub fn user_id(&self) -> &str
|
||||
}
|
||||
```
|
||||
|
||||
### `sf_auth_middleware`
|
||||
|
||||
```rust
|
||||
pub async fn sf_auth_middleware(
|
||||
config: SfAuthConfig,
|
||||
session: Session,
|
||||
req: Request,
|
||||
next: Next,
|
||||
) -> Response
|
||||
```
|
||||
|
||||
### `auth_callback`
|
||||
|
||||
```rust
|
||||
pub async fn auth_callback(
|
||||
session: Session,
|
||||
Query(params): Query<CallbackQuery>,
|
||||
) -> Result<Response, SfAuthError>
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **HTTPS Required**: Always use HTTPS in production for session security
|
||||
2. **Secure Sessions**: Configure session cookies with `secure` and `httponly` flags
|
||||
3. **Session Expiry**: Set appropriate session expiration times
|
||||
4. **Fail-Closed**: The middleware denies access on any validation errors
|
||||
|
||||
Example secure session configuration:
|
||||
|
||||
```rust
|
||||
use tower_sessions::Expiry;
|
||||
use time::Duration;
|
||||
|
||||
let session_layer = SessionManagerLayer::new(session_store)
|
||||
.with_secure(true)
|
||||
.with_http_only(true)
|
||||
.with_expiry(Expiry::OnInactivity(Duration::hours(2)));
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
Loading…
Add table
Add a link
Reference in a new issue