Authentication

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. 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

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:

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:

#* @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).

#* @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.