A practical end-to-end pass over auth in a Veap app: sign-up, sign-in, guarding pages, and recovery flows. The API details live in the auth chapter; this guide ties them into pages and forms.
Enable auth#
The generated composition root already includes auth:
Application.configure()
.withDatabase()
.withAuth() // users, sessions, RBAC, verification, reset
.create();Auth needs the database: the provider registers the user/session/role models' migrations through the migration system.
Sign-up and sign-in (Server Actions)#
Framework Server Actions are exported from @veap/core/auth/server:
"use server";
import { loginAction, registerAction } from "@veap/core/auth/server";
// call these directly from forms, or wrap them:A minimal sign-in page:
// app or plugin page
import { loginAction } from "@veap/core/auth/server";
export default function LoginPage() {
return (
<form action={loginAction}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Sign in</button>
</form>
);
}Under the hood: zod validation schemas from @veap/core/auth validate credentials, AuthService verifies the password hash through the PASSWORD_HASHER port, SessionService creates a session and sets an httpOnly cookie through COOKIE_STORE.
Reading the current user#
In Server Components and actions:
import { getCurrentUser, requireUser } from "@veap/core/auth/server";
export default async function ProfilePage() {
const user = await getCurrentUser(); // AuthUser | null
if (!user) {
// render a guest view or redirect
}
return <p>Signed in as {user.email}</p>;
}For pages that must not render for guests at all:
const user = await requireUser(); // throws AppError.UnauthorizedIn client components:
"use client";
import { useUser } from "@veap/core/react";
export function UserBadge() {
const user = useUser();
if (!user) return null;
return <span>{user.email}</span>;
}useUser() reads from AuthProvider, which the root provider tree (AppProvider) hydrates with the server session.
Guarding routes#
Two layers are available:
- Route middlewares in the Veap pipeline -
EnsuredAuth,EnsuredGuest,EnsuredUserfrom@veap/core/router/server. Attach them to plugin routes; they run before the page renders and redirect or reject early. - Facades in the page/action -
requireUser(),requireRole("admin"),requirePermission("posts.edit"). ThrowingAppError.Forbiddenrenders the error boundary with the mapped status.
Prefer middlewares for coarse area guards and facades for fine-grained checks inside the handler.
Roles and permissions#
import {
hasRole,
hasPermission,
assignRole,
revokeRole,
} from "@veap/core/auth/server";
// grant
await assignRole(userId, "editor");
// check in an action
if (!(await hasPermission(userId, "posts.publish"))) {
throw AppError.Forbidden();
}Roles and permissions are stored through RbacService; the full API, Server Action protection patterns, API route middlewares, and the event flow (system:auth:*) are in RBAC.
Email verification and password reset#
Both flows are facade-driven and mail-backed:
import {
sendVerificationEmail,
verifyEmail,
sendPasswordResetEmail,
resetPassword,
} from "@veap/core/auth/server";sendVerificationEmail(email)creates a token and sends the message through the configured mail transport (MAIL_TRANSPORT=consoleprints it locally instead of sending).verifyEmail(email, code)confirms the address.sendPasswordResetEmail(email)+resetPassword(token, newPassword)complete recovery.- Messages are built by the auth mailables and sent through the
IMailerport; see Email and reset.
Sign-out#
import { logoutAction } from "@veap/core/auth/server";
// or the facade:
import { logout } from "@veap/core/auth/server";logout() destroys the session row and clears the cookie through the port.
Extending#
- Swap
PASSWORD_HASHERto change hashing (see Custom providers). - Add profile fields by extending the user model and repository bindings (see Extending auth).
- React to registrations with
eventBus.subscribe("system:auth:user-registered", ...).