Tokens and Abilities
Tokens carry a list of abilities — arbitrary strings such as
todos:read or admin:* — that gate what the token may do. This
matches Laravel Sanctum’s “abilities” concept; the term is used in
preference to scope to avoid overloading an already-loaded word.
Issuing a scoped token
Pass an abilities list when creating the token:
// Full accesslet nt = user.create_token("full", vec!["*".into()], None).await?;
// Read-only on todoslet nt = user.create_token("todos-read", vec!["todos:read".into()], None).await?;
// Multiplelet nt = user.create_token("editor", vec![ "todos:read".into(), "todos:write".into(),], None).await?;The wildcard "*" satisfies every ability check.
Token format
Plain token returned to the client:
{token_id}|{prefix}{40_alphanumerics}{crc32b}token_id— the row’s primary keyprefix—config/sanctum.toml’stoken_prefix(empty by default)- 40 random alphanumerics — the entropy
crc32b— 8-char checksum for scanner recognition
Only the SHA-256 of everything after the pipe is stored in the DB. Verification uses constant-time comparison to defeat timing attacks.
Gating with middleware
use oxide_auth::{auth_middleware, abilities, ability};
use crate::app::domain::user::models::User;
// Require EVERY listed ability:app.middleware([auth_middleware::<User>(), abilities(["todos:write"])]) .post("/todos", TodoController::store);
// Require ANY of the listed abilities:app.middleware([auth_middleware::<User>(), ability(["todos:read", "todos:write"])]) .get("/todos", TodoController::index);auth_middleware::<User>() must come first — it populates the token
into the request extensions so abilities and ability can read it.
Inline ability check
If you need finer control inside a handler:
use oxide_auth::Authed;
pub async fn delete(req: Request) -> Response { let authed = req.extensions().get::<Authed<User>>().expect("authed"); if !authed.token.can("todos:delete") { return Response::forbidden_with("Missing ability: todos:delete"); } // ... Response::text("ok")}token.can(ability) returns true if the token’s abilities array
contains "*" or the exact ability.
Expiration
Set per-call:
use chrono::{Utc, Duration};
let nt = user.create_token( "temp", vec!["*".into()], Some(Utc::now() + Duration::hours(1)),).await?;Or globally via config/sanctum.toml:
expiration = "${SANCTUM_EXPIRATION:-60}" # minutes; 0 = never expiresA token whose expires_at has passed returns 401 on verification.
Revoking
use oxide_auth::Auth;
// Revoke by id (e.g. logout of current device):let Some(id) = Auth::token_id_from(&req) else { return Response::unauthorized();};Auth::revoke(id).await?;To revoke every token belonging to a user, filter by tokenable_id
and delete:
use oxide_auth::{PersonalAccessToken, PersonalAccessTokenColumn};use oxide_db::conn;use oxide_db::sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
PersonalAccessToken::delete_many() .filter(PersonalAccessTokenColumn::TokenableType.eq("User")) .filter(PersonalAccessTokenColumn::TokenableId.eq(user.id)) .exec(conn()) .await?;