# rok-auth

> JWT authentication with token refresh, email verification, password reset, TOTP, and passkeys for Axum.

Part of the [Rok Framework](https://rok.rs) — a full-stack Rust web framework built on Axum 0.8 and SQLx 0.8.

[![crates.io](https://img.shields.io/crates/v/rok-auth)](https://crates.io/crates/rok-auth) [![docs.rs](https://img.shields.io/docsrs/rok-auth)](https://docs.rs/rok-auth) [![MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

## Features

- JWT access + refresh token pair with configurable TTLs (HS256 / RS256)
- `AuthUser` and `OptionalAuthUser` Axum extractors for protected routes
- `AuthLayer` Tower middleware that validates bearer tokens on every request
- Token blacklisting for logout and refresh revocation (PostgreSQL-backed)
- Email verification flow with time-limited signed tokens
- Password reset flow with secure token generation and expiry
- TOTP (authenticator app) two-factor authentication
- Magic link passwordless login (feature-gated)
- Passkey / WebAuthn credential storage (feature-gated)
- Device trust tokens ("remember this device") (feature-gated)

## Installation

```toml
[dependencies]
rok-auth = { version = "0.2", features = ["axum"] }
```

## Quick Start

```rust
use rok_auth::{Auth, AuthConfig, AuthUser, TokenPair};
use axum::{extract::State, response::IntoResponse, Json, Router, routing::{get, post}};

#[tokio::main]
async fn main() {
    let auth = Auth::new(AuthConfig {
        secret: std::env::var("JWT_SECRET").unwrap(),
        access_ttl_secs: 900,       // 15 minutes
        refresh_ttl_secs: 604_800,  // 7 days
        issuer: Some("myapp".into()),
    });

    let app = Router::new()
        .route("/auth/login", post(login))
        .route("/me", get(me))
        .with_state(auth);

    axum::serve(tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(), app)
        .await
        .unwrap();
}

async fn login(State(auth): State<Auth>, /* Form or Json body */ ) -> impl IntoResponse {
    let pair: TokenPair = auth.issue(user_id, serde_json::json!({ "role": "member" })).await?;
    Json(pair)
}

// AuthUser rejects with 401 if the bearer token is missing or invalid
async fn me(auth: AuthUser) -> impl IntoResponse {
    Json(serde_json::json!({ "id": auth.sub, "role": auth.claims["role"] }))
}
```

## Core API

### Token pair

```rust
// Issue
let pair: TokenPair = auth.issue(user_id, extra_claims).await?;
// pair.access_token  — signed JWT, short-lived
// pair.refresh_token — opaque token stored in DB, long-lived

// Refresh
let new_pair: TokenPair = auth.refresh(&pair.refresh_token).await?;

// Revoke (logout)
auth.revoke(&pair.refresh_token).await?;

// Verify and decode manually
let claims = auth.verify(&pair.access_token)?;
println!("sub: {}", claims.sub);
```

### Email verification

```rust
use rok_auth::EmailVerification;

// Issue a token and send it in an email link
let token = EmailVerification::issue(&pool, user_id).await?;
// GET /auth/verify?token=<token>
EmailVerification::verify(&pool, &token).await?;  // marks email_verified_at
```

### Password reset

```rust
use rok_auth::PasswordReset;

let token = PasswordReset::issue(&pool, &user_email).await?;
// email the link: https://app.example.com/reset?token=<token>

PasswordReset::reset(&pool, &token, &new_argon2_hash).await?;
```

### TOTP (two-factor)

```rust
use rok_auth::Totp;

// Setup
let secret = Totp::generate_secret();
let qr_url  = Totp::otpauth_url(&secret, &user_email, "MyApp");
Totp::save(&pool, user_id, &secret).await?;

// Verify a code from the authenticator app
Totp::verify(&pool, user_id, &submitted_code).await?; // Err if invalid or expired
```

## Feature Flags

| Flag | Description | Default |
|------|-------------|---------|
| `axum` | `AuthUser`, `OptionalAuthUser` extractors and `AuthLayer` middleware | No |
| `macros` | `#[require_auth]`, `#[require_role("...")]` proc-macro guards | No |
| `magic-link` | Passwordless magic link authentication | No |
| `passkeys` | WebAuthn / FIDO2 credential storage | No |
| `device-trust` | Remember-this-device long-lived tokens | No |

## Integration

`rok-auth` integrates with `rok-orm` for token persistence and with `rok-mail` for sending verification and password reset emails. Migrations for all auth tables are embedded and registered via `rok_auth::migrations()`.

```rust
// Apply bundled migrations
MigrationRunner::new(pool.clone())
    .source(rok_auth::migrations())  // creates personal_access_tokens, password_resets, etc.
    .run()
    .await?;

// Wire the AuthLayer globally so all routes can use AuthUser
let app = Router::new()
    .route("/me", get(me))
    .layer(rok_auth::AuthLayer::new(auth.clone()));
```

`rok-acl` and `rok-bouncer` both accept `AuthUser` directly, so role and policy checks compose cleanly with auth.

## License

MIT
