leafs/auth Β· framework agnostic

Sign-in you can finish
before lunch.

Register, login, sessions or JWTs, roles, permissions and OAuth β€” on your database, in any PHP app. No identity provider, no monthly bill, no user table you don't control.

auth.php
$auth = new Leaf\Auth;
$auth->connect(['dbname' => 'app', ...]);
// the whole sign-in
$ok = $auth->login([
  'email' => $email,
  'password' => $password,
]);
if (!$ok) return $auth->errors();
$user = $auth->user();
$token = $auth->tokens()['access'];
app.test/login signed out

Welcome back

Try a wrong password too β€” errors are yours to render.

M

Mika

mika@leafphp.dev

admin

$user

{ "id": 1, "username": "mika",
  "email": "mika@leafphp.dev" }
// password + id hidden by default

access token

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyLmlkIjox…

Sign in on the right β€” the code lights up as it runs. Wrong password shows the real error shape.

The Leaf syntax πŸƒ

Inside Leaf, auth is one word.

No classes, no setup, no instances to pass around β€” auth() is simply there in every route, with guards and the signed-in user one call away. Watch it write itself.

app.php
functional mode

              
app.test Β·

POST /register

M

Mika

mika@leafphp.dev

account created

Password hashed, duplicates rejected, user signed in β€” one call.

routes

GET / public
GET /dashboard πŸ”’ auth.required
GET /login auth.guest

Guests hitting /dashboard get redirected. No if-statements in your handlers.

GET /dashboard β†’ 200

{
  "username": "mika",
  "email": "mika@leafphp.dev"
}

The signed-in user, anywhere β€” auth()->user(), or even request()->user().

Prefer a class? new Leaf\Auth() gives you the exact same API in any PHP app β€” see "bring your stack" below.

"I just want users to log in."

Somewhere along the way, logging someone in became a platform decision β€” a vendor, a dashboard, a per-user price, a user table you can't query. Auth is the boring alternative: your database, your users table, a few method calls, done.

What's in the box

Everything a login needs. Nothing it doesn't.

Register & login

Two calls, hashed passwords, duplicate checks and readable errors included.

Sessions or JWTs

One config flag switches between cookie sessions and stateless tokens.

Roles & permissions

Define roles once, then ask can(), is() anywhere.

OAuth providers

Google out of the box, any league/oauth2 client through one method.

Your data

Your users table. Your database.

Auth writes to a normal table you can query, join, export and back up like anything else. Point it at MySQL, Postgres, SQLite or SQL Server β€” or hand it a PDO connection you already have open.

  • βœ“ Rename the table, the id, the password column
  • βœ“ Hidden fields never leave the module
  • βœ“ Timestamps handled, or turned off
config.php
$auth->config([
  'db.table' => 'accounts',
  'id.key' => 'account_id',
  'password.key' => 'secret',
  'unique' => ['email', 'username'],
  'hidden' => ['field.id', 'field.password'],
  'timestamps' => true,
]);

// already have a connection? hand it over
$auth->dbConnection($pdo);
roles.php
assign:
$auth->createRoles([
  'admin' => ['posts.write', 'users.manage'],
  'editor' => ['posts.write'],
  'viewer' => [],
]);

$user->assign('editor');

$user->can('posts.write');   // true
$user->cannot('users.manage'); // true
$user->is('editor');          // true

Authorisation

Who can do what, in plain English.

Roles are a map of names to permissions β€” nothing more. Assign them to users, then ask questions that read like the sentence you were already saying out loud in standup.

Social login

"Sign in with Google" without the yak shave.

Point auth at your OAuth credentials and it handles the redirect dance, then hands you the same user object as a password login β€” same table, same tokens, same user() call. Any league/oauth2 provider works through withProvider().

google.php
$auth->withGoogle([
  'clientId' => _env('GOOGLE_ID'),
  'clientSecret' => _env('GOOGLE_SECRET'),
  'redirectUri' => '/auth/callback',
], function ($user) {
  // same user object as a password login
  return response()->json($user);
});

Batteries where it counts

Everything around the login, handled.

The parts you'd otherwise write twice a year, already here.

Password changes

Old-password checks and resets, hashed the same way as registration.

$auth->updatePassword($old, $new);

Email verification

Purpose-scoped tokens you can mail out, verify, and check.

$user->generateVerificationToken();
$user->isVerified();

Guards for routes

Decide what happens to guests and to already-signed-in users.

$auth->middleware('auth.required',
  fn () => response()->redirect('/login'));

Readable errors

Field-keyed messages, customisable copy, ready to render.

$auth->errors();
// ['password' => 'Password is incorrect!']

Token control

Access + refresh tokens, your secret, your lifetimes.

$auth->tokens();
// ['access' => …, 'refresh' => …]

One require away

No identity provider, no dashboard, no per-user pricing. Ever.

composer require leafs/auth

Bring your stack

It runs in whatever you're building.

Auth needs PHP, PDO and a users table. Use the Leaf\Auth class anywhere; the extra Leaf niceties light up when Leaf is present.

Leaf
Laravel
Symfony
Slim
WordPress
Plain PHP
+ your stack

Ship the login.
Get back to the product.

$ composer require leafs/auth

Then read the full documentation.