--- title: "Authentication" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Authentication} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(eval = FALSE) ``` Auth in aurora is **pluggable and opt-in** — it is never baked into `aurora_app()`. aurora ships one scheme: a stateless **JWT delivered as an `HttpOnly` cookie**, signed with [jose](https://cran.r-project.org/package=jose). It stays stateless (the token is self-contained, so any replica can validate it) and is wired entirely in your app's annotated files. ## Scaffold ```{r} library(aurora) aurora_create_app("meu_app", template = "auth") aurora_run("meu_app") # log in with admin / admin123 ``` ## How it is wired Three pieces, all in the app — `aurora_app()` needs no auth knowledge: **1. The scheme (a helper).** `helpers/auth_setup.R` defines the scheme and your user lookup: ```r auth <- aurora_auth_jwt(secret = Sys.getenv("AURORA_JWT_SECRET", "dev-only")) secure_cookies <- identical(Sys.getenv("AURORA_ENV"), "prod") find_user <- function(name) ... # your DB / store; passwords hashed with sodium ``` **2. The gate (a header route).** `routers/guard.R` runs before every `/api/*` handler — and before the request body is even received — rejecting anyone without a valid token with a clean `401`: ```r #* @any /api/* #* @header function(request) { aurora_jwt_guard(auth, request) # 401 via reqres::abort_unauthorized() if invalid plumber2::Next # otherwise continue the chain } ``` **3. Public login/logout (not under `/api`, so the gate ignores them).** ```r #* @post /auth/login #* @parser json #* @serializer json function(request, response, body = NULL) { u <- find_user(body$user) if (is.null(u) || !sodium::password_verify(u$hash, body$pass %||% "")) { response$status <- 401L return(list(error = "Usuario ou senha invalidos.")) } token <- aurora_jwt_token(auth, list(user = u$name)) aurora_set_auth_cookie(auth, response, token, secure = secure_cookies) list(user = u$name) } #* @post /auth/logout #* @serializer json function(response) { aurora_clear_auth_cookie(auth, response, secure = secure_cookies) list(status = "ok") } ``` ## The frontend The cookie is `HttpOnly`, so JavaScript never touches the token. `app.js` simply probes `GET /api/me` on load (reveal the app on success), posts to `/auth/login` / `/auth/logout`, and shows the login overlay whenever any `/api` call returns `401` via the runtime's `aurora.onUnauthorized` hook. ## Helpers | Function | Purpose | |---|---| | `aurora_auth_jwt(secret, cookie, expiry)` | define the scheme | | `aurora_jwt_token(auth, claims)` | mint a signed token (login) | | `aurora_jwt_decode(auth, token)` | verify; returns payload or `NULL` | | `aurora_jwt_guard(auth, request)` | gate: `401` unless valid (use in a `@header` route) | | `aurora_set_auth_cookie()` / `aurora_clear_auth_cookie()` | manage the cookie | ## Why not a fireproof guard? plumber2's native `api_auth_guard()` + `fireproof` guards target upstream identity providers (OAuth/OIDC) or shared static keys, require the `firesale` datastore plugin, and return `400`/`403`. A self-issued JWT-cookie session needs a uniform `401` for both absent and expired sessions (cookie expiry looks like "absent") and no extra infrastructure — so aurora uses a `@header` guard + `reqres::abort_unauthorized()`, the migration-endorsed replacement for v1 filters. ## Production checklist - Set a strong `AURORA_JWT_SECRET` (never commit it). - Set `AURORA_ENV=prod` behind HTTPS for `Secure; SameSite=Strict` cookies. - Replace the demo `find_user()` with your real, `sodium`-hashed user store.