Credential Authentication
The classic, robust approach. Enable users to sign up and log in using their email and password, featuring secure storage, hashing, and built-in validation.
Persistence
Simple data modeling using Prisma ORM schemas.
Security
Password hashing and automatic CSRF protection handled by the framework.
Full Stack
Colocate your backend logic and frontend forms in a single file.
Database Schema
First, define the user model in your schema.prisma. You will need fields for credentials and roles to support the AuthRole enum.
model User { id String @id @default(cuid()) name String? email String? @unique password String? // Password Hash emailVerified DateTime? image String? roleId Int? userRole UserRole? @relation(fields: [roleId], references: [id]) @@map("Users") } model UserRole { id Int @id @default(autoincrement()) name String @unique user User[] }
npx ppo generate
User Registration
Create the route src/app/auth/register/index.php. We use the #[Exposed] attribute to allow the frontend to call the PHP function directly.
<?php use Lib\Auth\Auth; use Lib\Prisma\Classes\Prisma; use PP\Attributes\Exposed; use PP\Request; use PP\Validator; // Redirect if already logged in if (Auth::getInstance()->isAuthenticated()) { Request::redirect('/dashboard'); } #[Exposed] function register($data) { $email = Validator::email($data->email); $pass = Validator::string($data->password); if (!$email || !$pass) return ['error' => 'Invalid data']; $prisma = Prisma::getInstance(); // Check existence if ($prisma->user->findUnique([ 'where' => ['email' => $email] ])) { return ['error' => 'Email already exists']; } // Create User $prisma->user->create([ 'data' => [ 'email' => $email, 'password' => password_hash($pass, PASSWORD_DEFAULT), 'userRole' => [ 'connectOrCreate' => [ 'where' => ['name' => 'User'], 'create' => ['name' => 'User'] ] ] ] ]); Request::redirect('/auth/login'); } ?>
<div class="max-w-md mx-auto mt-10">
<form onsubmit="register(event)">
<input value="{email}" oninput="setEmail(event.target.value)"
name="email" type="email" />
<input value="{password}" oninput="setPassword(event.target.value)"
name="password" type="password" />
<button type="submit" disabled="{busy}">
{busy ? 'Creating...' : 'Register'}
</button>
</form>
<p hidden="{!error}" class="text-red-500">{error}</p>
</div>
<script>
const [email, setEmail] = pp.state('');
const [password, setPassword] = pp.state('');
const [error, setError] = pp.state('');
const [busy, setBusy] = pp.state(false);
async function register(event) {
event.preventDefault();
setBusy(true);
setError('');
try {
// On success the PHP side calls Request::redirect(...)
// and pp.rpc performs the navigation for us.
const response = await pp.rpc('register', { email, password });
if (response?.error) setError(response.error);
} catch (e) {
setError(e.message);
} finally {
setBusy(false);
}
}
</script>
Sign In
In src/app/auth/login/index.php, we verify credentials and use the Auth::signIn method to establish the session.
<?php use Lib\Auth\Auth; use Lib\Prisma\Classes\Prisma; use PP\Attributes\Exposed; #[Exposed] function login($data) { $email = $data->email; $pass = $data->password; // Fetch user and include Role relation $user = Prisma::getInstance()->user->findUnique([ 'where' => ['email' => $email], 'include' => ['userRole' => true] ]); if (!$user || !password_verify($pass, $user->password)) { return ['error' => 'Invalid credentials']; } // Sign in with data and Role name Auth::getInstance()->signIn([ 'id' => $user->id, 'role' => $user->userRole->name ?? 'User' ]); Request::redirect('/dashboard'); } ?> <!-- Simple HTML Form --> <form onsubmit="login" class="space-y-4"> <input name="email" placeholder="user@example.com" /> <input name="password" type="password" /> <button>Sign In</button> </form>