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.

1

Database Schema

First, define the user model in your schema.prisma. You will need fields for credentials and roles to support the AuthRole enum.

prisma/schema.prisma Prisma Schema
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
Generates the PHP classes
2

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.

Backend Logic
<?php
use Lib\Auth\Auth;
use Lib\Prisma\Classes\Prisma;
use Lib\Validator;
use PP\Attributes\Exposed;

// 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');
}
?>
Frontend UI
<div class="max-w-md mx-auto mt-10">
  <form onsubmit="register" pp-after-request="onReg">
    
    <!-- Inputs -->
    <input name="email" type="email" />
    <input name="password" type="password" />
    
    <button>Register</button>
  </form>

  <p id="error-msg" class="text-red-500"></p>
</div>

<script>
  function onReg(response) {
    if (response.error) {
       document.getElementById('error-msg')
         .innerText = response.error;
    }
  }
</script>
3

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>