Auth
Vyuh auth is opt-in and handler-signature driven. If a route does not extract
AuthUser, permit!(...), or ApiKey, Vyuh does no authentication work for
that route.
Vyuh does not provide user models, registration, password reset, API-key tables, session tables, or account management flows. Applications own identity storage and decide how users or API keys are verified.
Vyuh auth deliberately stops at verified principals. Applications own user records, account lifecycle, and domain permissions.
Mental Model
| Need | Use |
|---|---|
| Public route | no auth type |
| Authenticated JWT user | AuthUser |
| Static role mask | permit!(Role, Admin) |
| Dynamic permission | handler or service logic |
| Machine-to-machine auth | ApiKey |
| Issue or refresh JWTs | site.auth() |
| Django password hashes | make_password, check_password |
Roles are optional. The permit! macro is a static role-mask convenience, not a
general authorization framework. Omit roles entirely when an application does
not need them.
Console roles live under vyuh::console and are separate from application
roles. They only control access to Vyuh’s optional read-only console APIs.
Configuration
Auth configuration lives on SiteConf. Cookies are disabled by default:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::{AuthConf, CookieConf};
let conf = SiteConf::default()
.secret_key("replace-with-a-long-random-secret")
.auth(
AuthConf::default()
.access_ttl(3600)
.refresh_ttl(604800)
.access_cookie(CookieConf::new("access_token"))
.refresh_cookie(CookieConf::new("refresh_token")),
);
}
By default, JWTs are signed with HS256 using SiteConf.secret_key.
SiteConf::validate() checks the configured minimum length for HMAC signing
keys.
Useful AuthConf options:
access_ttl(seconds)andrefresh_ttl(seconds).issuer(value)to require and emit an issuer claim.audience(policy)for optional, required, or disabled audience checks.leeway_seconds(seconds)for clock skew.min_secret_len(len)for signing-secret validation.jwt(...)for algorithm and key configuration.access_cookie(...)andrefresh_cookie(...)for opt-in cookies.api_keys(...)for API-key verification.
JWT Users
Use site.auth() to issue tokens:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::{AuthError, AuthUser, TokenPair};
async fn login(site: Site) -> Result<Json<TokenPair>, AuthError> {
let user = AuthUser::new("user-123", 0);
let tokens = site.auth().create_token_pair(user, &["web"])?;
Ok(Json(tokens))
}
}
Extract AuthUser to require an access token:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::AuthUser;
async fn me(user: AuthUser) -> Json<String> {
Json(user.key.to_string())
}
}
AuthUser contains:
key: the authenticated subject, stored as JWTsub.roles: au64static role mask.
Access and refresh tokens are distinct. AuthUser accepts access tokens only,
and site.auth().refresh(...) accepts refresh tokens only.
Access tokens can be sent with:
Authorization: Bearer <jwt>
The older Authorization: JWT <jwt> form is also accepted.
JWT Algorithms
Vyuh defaults to HS256 with SiteConf.secret_key. That matches the common
Django Simple JWT default of HS256 with Django’s SECRET_KEY. Django core
itself does not use JWT for its built-in sessions; its signing utilities use
SHA-256 signed values and cookies.
Use JwtConf when deployments need a different symmetric algorithm or an
asymmetric key pair:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::{AuthConf, JwtConf, JwtKeySource};
let auth = AuthConf::default().jwt(JwtConf::hs512(JwtKeySource::Env(
"JWT_SECRET".into(),
)));
}
Asymmetric deployments can sign with a private PEM and verify with a public
PEM. Relative file paths are resolved from SiteConf.project_dir:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::{AuthConf, JwtConf, JwtKeySource};
let auth = AuthConf::default().jwt(JwtConf::rs256(
JwtKeySource::File("secrets/jwt-private.pem".into()),
JwtKeySource::File("secrets/jwt-public.pem".into()),
));
}
Supported algorithms are HS256, HS384, HS512, RS256, RS384,
RS512, ES256, ES384, and EdDSA. HMAC algorithms use one symmetric key
and reject a separate verifying key. RSA, ECDSA, and EdDSA configurations
require both signing and verifying keys. Token decoding accepts only the
configured algorithm.
Cookies
Cookies are opt-in. When configured, login_user(...) writes access and refresh
cookies, and logout(...) clears them:
#![allow(unused)]
fn main() {
let conf = SiteConf::default().auth(
AuthConf::cookie_pair("access_token", "refresh_token")
);
}
CookieConf uses typed CookieSameSite values. Invalid SameSite strings are
not silently accepted.
Static Roles
Static role checks are useful for simple route gates:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::{AuthUser, BitRole, permit};
#[derive(BitRole)]
enum AppRole {
Manager,
Editor,
Viewer,
}
async fn managers_only(_permit: permit!(AppRole, Manager)) {}
}
Role expressions support:
permit!(AppRole, Manager)- requires one role.permit!(AppRole, Manager | Editor)- requires any listed role.permit!(AppRole, Manager & Editor)- requires all listed roles.
permit! returns 401 for missing or invalid tokens and 403 when the token
is valid but lacks the required role mask. It also contributes role metadata to
OpenAPI.
Use handler or service logic for dynamic authorization:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::AuthUser;
async fn edit_post(
site: Site,
user: AuthUser,
Path(id): Path<i64>,
) -> Result<Json<PostOut>, Error> {
let post = load_post(site.db(), id).await?;
if post.owner_id != user.key.as_ref() {
return Err(Error::new(vyuh::ErrorKind::Forbidden).with_context("not allowed"));
}
Ok(Json(post.into()))
}
}
API Keys
API keys are for machine-to-machine authentication. Vyuh extracts keys, but the application verifies them through a hook. Vyuh does not store plaintext keys or own API-key tables.
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::{ApiKey, ApiKeyPrincipal, ApiKeyVerifier, AuthError};
struct MyVerifier;
impl ApiKeyVerifier for MyVerifier {
async fn verify(&self, presented: &str) -> Result<ApiKeyPrincipal, AuthError> {
if presented == "secret" {
Ok(ApiKeyPrincipal::new("key-1").subject("service-1"))
} else {
Err(AuthError::InvalidApiKey)
}
}
}
async fn ingest(key: ApiKey) {
let key_id = key.key_id.to_string();
}
}
Configure the verifier:
#![allow(unused)]
fn main() {
use vyuh::prelude::*;
use vyuh::auth::{ApiKeyConf, AuthConf};
let auth = AuthConf::default().api_keys(
ApiKeyConf::default().verifier(MyVerifier)
);
}
By default, API keys are read from X-API-Key and
Authorization: ApiKey <key>. Query-string API keys are disabled by default
because URLs are commonly logged and copied. Enable them only when the protocol
requires it.
OpenAPI
Auth is reflected in generated OpenAPI specs from handler arguments:
AuthUseraddsbearerAuth.permit!(Role, ...)addsbearerAuthwith role scopes.ApiKeyaddsapiKeyAuth.
Vyuh emits standard security schemes:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
apiKeyAuth:
type: apiKey
in: header
name: X-API-Key
OpenAPI spec endpoints can be protected independently with
OpenApiConf::auth(...), which receives the extracted AuthUser.
Password Utilities
Vyuh includes small PBKDF2 password helpers:
make_password(password, salt, algorithm)check_password(password, encoded)unusable_password()
The encoded hash format is compatible with Django PBKDF2 hashes. Django compatibility means password-hash compatibility and migration friendliness, not Django sessions, permissions, groups, or user tables.
Examples
auth_jwt_basic.rs: issue JWTs and protect a route withAuthUser.auth_cookies.rs: opt-in cookies and refresh flow.auth_roles_static.rs: static role masks andpermit!.auth_dynamic_permission.rs: dynamic authorization in handler code.auth_api_key.rs: verifier-backed API-key route.auth_api_key_openapi.rs: API-key OpenAPI security metadata.
Failure Modes
- Missing JWTs return
AuthError::MissingTokenand HTTP401. - Invalid JWTs return
AuthError::InvalidTokenand HTTP401. - Expired JWTs return
AuthError::ExpiredTokenand HTTP401. - Access/refresh token-kind mismatch returns
AuthError::WrongTokenKindand HTTP401. - Missing API keys return
AuthError::MissingApiKeyand HTTP401. - Invalid API keys return
AuthError::InvalidApiKeyand HTTP401. - Failed audience, role, or permission checks return
AuthError::Forbiddenand HTTP403.
Current Limitations
- Auth is stateless unless the application stores extra session or token data.
- API-key storage and revocation are application responsibilities.
- JWK and JWKS fetching are not included in this pass.
- Vyuh does not ship user models, registration, password reset, or account management flows.
- Role masks are limited to 64 bit positions.