ApnaPHP

Documentation

Authentication

ApnaPHP provides a built-in authentication system that handles user login, registration, password management, and session handling out of the box.

1. Basic Authentication

Check if User is Logged In

<?php

if (auth()->check()) {
    // User is logged in
    $user = auth()->user();
    echo "Welcome, " . $user['name'];
} else {
    // User is not logged in
    return redirect('/login');
}

Login User

<?php
// app/login/page.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

function POST(Request $request): Response
{
    $credentials = [
        'email' => $request->input('email'),
        'password' => $request->input('password')
    ];

    $remember = $request->input('remember') === 'on';

    if (auth()->attempt($credentials, $remember)) {
        // Login successful
        return redirect('/dashboard')->withSuccess('Welcome back!');
    } else {
        // Login failed
        return redirect('/login')->withError('Invalid credentials');
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <form method="POST">
        <?= csrfField() ?>
        
        <input type="email" name="email" value="<?= old('email') ?>" required>
        <input type="password" name="password" required>
        <label>
            <input type="checkbox" name="remember">
            Remember Me
        </label>
        <button type="submit">Login</button>
    </form>
</body>
</html>

Register User

<?php
// app/register/page.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

function POST(Request $request): Response
{
    try {
        $validated = validate($request->all(), [
            'name' => 'required|min:3|max:255',
            'email' => 'required|email',
            'password' => 'required|min:8|confirmed'
        ]);

        // Register user
        $user = auth()->register($validated);

        // Auto-login after registration
        auth()->attempt([
            'email' => $validated['email'],
            'password' => $validated['password']
        ]);

        return redirect('/dashboard')->withSuccess('Account created successfully!');

    } catch (ValidationException $e) {
        return redirect('/register')->withErrors($e->errors())->withInput();
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Register</title>
</head>
<body>
    <form method="POST">
        <?= csrfField() ?>
        
        <input type="text" name="name" value="<?= old('name') ?>" required>
        <input type="email" name="email" value="<?= old('email') ?>" required>
        <input type="password" name="password" required>
        <input type="password" name="password_confirmation" required>
        <button type="submit">Register</button>
    </form>
</body>
</html>

Logout User

<?php
// app/logout/page.apna.php

auth()->logout();
return redirect('/')->withSuccess('Logged out successfully!');

2. Authentication Helpers

ApnaPHP provides many authentication helper functions:

auth()

Get authentication instance.

$authInstance = auth();

user()

Get current authenticated user.

$user = user(); // Returns user array or null
echo $user['name'];
echo $user['email'];

userId()

Get current user ID.

$id = userId(); // Returns int or null

userEmail()

Get current user email.

$email = userEmail(); // Returns string or null

userName()

Get current user name.

$name = userName(); // Returns string or null

userRole()

Get current user role.

$role = userRole(); // Returns string or null

isLoggedIn()

Check if user is logged in.

if (isLoggedIn()) {
    // User is authenticated
}

isGuest()

Check if user is guest (not logged in).

if (isGuest()) {
    // User is not authenticated
}

isAdmin()

Check if current user is admin.

if (isAdmin()) {
    // User is admin
}

hasRole()

Check if current user has specific role.

if (hasRole('editor')) {
    // User has editor role
}

3. Protecting Routes with Middleware

Require Authentication

<?php
// app/dashboard/middleware.apna.php

if (!auth()->check()) {
    return redirect('/login')->withError('Please login to continue.');
}

Require Specific Role

<?php
// app/admin/middleware.apna.php

if (!auth()->check()) {
    return redirect('/login')->withError('Please login to continue.');
}

if (!auth()->isAdmin()) {
    return abort(403, 'Admin access required');
}

Using Helper Functions

<?php
// app/admin/middleware.apna.php

requireAuth(); // Redirects to /login if not authenticated

requireAdmin(); // Redirects to /unauthorized if not admin

requireRole('editor'); // Redirects to /unauthorized if not editor

4. Password Management

Hash Password

$hashedPassword = hashPassword('mypassword');
// Or
$hashedPassword = auth()->hashPassword('mypassword');

Verify Password

if (verifyPassword('mypassword', $hashedPassword)) {
    // Password is correct
}
// Or
if (auth()->verifyPassword('mypassword', $hashedPassword)) {
    // Password is correct
}

Change Password

<?php
// app/settings/password/page.apna.php

function POST(Request $request): Response
{
    try {
        $validated = validate($request->all(), [
            'current_password' => 'required',
            'new_password' => 'required|min:8|confirmed'
        ]);

        if (changePassword($validated['current_password'], $validated['new_password'])) {
            return redirect('/settings')->withSuccess('Password changed successfully!');
        } else {
            return redirect('/settings/password')->withError('Current password is incorrect');
        }

    } catch (ValidationException $e) {
        return redirect('/settings/password')->withErrors($e->errors());
    }
}

Password Reset

Generate Reset Token:

<?php
// app/forgot-password/page.apna.php

function POST(Request $request): Response
{
    $email = $request->input('email');

    if (!isValidEmail($email)) {
        return redirect('/forgot-password')->withError('Invalid email address');
    }

    $token = generatePasswordResetToken($email);

    if ($token) {
        // Send email with reset link
        $resetLink = env('APP_URL') . "/reset-password?token={$token}";
        // mail($email, 'Password Reset', "Click here to reset: {$resetLink}");

        return redirect('/forgot-password')->withSuccess('Password reset link sent to your email');
    } else {
        return redirect('/forgot-password')->withError('Email not found');
    }
}

Reset Password with Token:

<?php
// app/reset-password/page.apna.php

function POST(Request $request): Response
{
    try {
        $validated = validate($request->all(), [
            'token' => 'required',
            'password' => 'required|min:8|confirmed'
        ]);

        if (resetPassword($validated['token'], $validated['password'])) {
            return redirect('/login')->withSuccess('Password reset successfully!');
        } else {
            return redirect('/reset-password')->withError('Invalid or expired token');
        }

    } catch (ValidationException $e) {
        return redirect('/reset-password')->withErrors($e->errors());
    }
}

5. Role-Based Access Control

Define Roles

In your User model:

<?php

namespace App\Models;

use ApnaPHP\Database\Model;

class User extends Model
{
    protected array $schema = [
        'name' => 'required|type:string|length:255',
        'email' => 'required|unique|type:string|length:255',
        'password' => 'required|type:string',
        'role' => 'type:string|default:user|length:50', // admin, editor, user
        'status' => 'type:string|default:active|length:50',
    ];

    public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }

    public function isEditor(): bool
    {
        return $this->role === 'editor';
    }

    public function can(string $permission): bool
    {
        $permissions = [
            'admin' => ['create', 'read', 'update', 'delete', 'manage_users'],
            'editor' => ['create', 'read', 'update'],
            'user' => ['read']
        ];

        return in_array($permission, $permissions[$this->role] ?? []);
    }
}

Check Permissions

<?php
// app/posts/edit/[id]/page.apna.php

if (!auth()->check()) {
    return redirect('/login');
}

$user = auth()->user();

// Check role
if ($user['role'] !== 'admin' && $user['role'] !== 'editor') {
    return abort(403, 'You do not have permission to edit posts');
}

// Or use helper
if (!hasRole('admin') && !hasRole('editor')) {
    return abort(403, 'Insufficient permissions');
}

6. API Authentication

Token-Based Authentication

<?php
// app/api/middleware.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

$token = $request->header('Authorization');

if (!$token) {
    return Response::json(['error' => 'Authorization header required'], 401);
}

// Remove 'Bearer ' prefix
$token = str_replace('Bearer ', '', $token);

// Validate token (example - in real app, check database)
$validToken = env('API_TOKEN', 'your-secret-token');

if ($token !== $validToken) {
    return Response::json(['error' => 'Invalid token'], 401);
}

// Token is valid, continue

Session-Based API Auth

<?php
// app/api/middleware.apna.php

if (!auth()->check()) {
    return Response::json(['error' => 'Unauthorized'], 401);
}

// Check role for API access
if (!auth()->isAdmin()) {
    return Response::json(['error' => 'Admin access required'], 403);
}

7. Security Features

CSRF Protection

<?php
// app/page.apna.php
?>

<form method="POST" action="/submit">
    <?= csrfField() ?> <!-- Generates hidden CSRF token field -->
    
    <input type="text" name="data">
    <button type="submit">Submit</button>
</form>

Verify CSRF Token:

<?php
// app/submit/page.apna.php

function POST(Request $request): Response
{
    $token = $request->input('_token');

    if (!verifyCsrfToken($token)) {
        return abort(419, 'CSRF token mismatch');
    }

    // Process form
}

Rate Limiting

<?php
// app/api/middleware.apna.php

$ip = $request->ip();
$rateLimitKey = "rate_limit:api:{$ip}";

// Get current count
$currentRequests = cache()->get($rateLimitKey, 0);

if ($currentRequests >= 100) { // 100 requests per hour
    return Response::json([
        'error' => 'Too Many Requests',
        'message' => 'Rate limit exceeded. Please try again later.'
    ], 429);
}

// Increment counter
cache()->put($rateLimitKey, $currentRequests + 1, 3600); // 1 hour TTL

Account Lockout

<?php
// app/login/page.apna.php

function POST(Request $request): Response
{
    $email = $request->input('email');

    // Check if account is locked
    if (auth()->isAccountLocked($email)) {
        return redirect('/login')->withError('Account locked due to too many failed attempts. Try again later.');
    }

    if (auth()->attempt($request->only(['email', 'password']))) {
        // Clear login attempts on success
        auth()->clearLoginAttempts($email);
        return redirect('/dashboard');
    } else {
        // Increment failed attempts
        auth()->incrementLoginAttempts($email);
        
        $attempts = auth()->getLoginAttempts($email);
        $remaining = 5 - $attempts;
        
        return redirect('/login')->withError("Invalid credentials. {$remaining} attempts remaining.");
    }
}

8. Session Management

Get Session Data

<?php

$sessionData = auth()->getSessionData();
// Returns:
// [
//     'logged_in' => true,
//     'user_id' => 1,
//     'user_email' => 'user@example.com',
//     'user_name' => 'John Doe',
//     'user_role' => 'admin',
//     'session_id' => 'abc123...'
// ]

Flash Messages

<?php

// Set flash message
flash('success', 'Operation completed successfully!');
flash('error', 'Something went wrong');

// Get flash message (and remove it)
$message = getFlash('success');

// Check if flash message exists
if (hasFlash('error')) {
    $error = getFlash('error');
}

Old Input

<?php
// Redirect with old input for form repopulation
return redirect('/register')->withInput()->withError('Validation failed');
?>

<!-- In the form -->
<input type="text" name="name" value="<?= old('name') ?>">
<input type="email" name="email" value="<?= old('email') ?>">

9. Complete Authentication Example

Here's a complete authentication flow:

Registration Page

<?php
// app/register/page.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;
use App\Models\User;

function POST(Request $request): Response
{
    try {
        $validated = validate($request->all(), [
            'name' => 'required|min:3|max:255',
            'email' => 'required|email',
            'password' => 'required|min:8|confirmed',
            'terms' => 'required|in:yes,on,1,true'
        ], [
            'terms.required' => 'You must accept the terms and conditions',
            'password.confirmed' => 'Password confirmation does not match'
        ]);

        // Create user
        $user = User::create([
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => $validated['password'], // Will be hashed by model
            'role' => 'user',
            'status' => 'active'
        ]);

        // Auto-login
        auth()->attempt([
            'email' => $validated['email'],
            'password' => $validated['password']
        ]);

        console_success('User registered', ['email' => $user->email]);

        return redirect('/dashboard')->withSuccess('Welcome to ApnaPHP!');

    } catch (ValidationException $e) {
        return redirect('/register')->withErrors($e->errors())->withInput();
    }
}

$errors = getFlash('errors', []);
?>

<!DOCTYPE html>
<html>
<head>
    <title>Register - ApnaPHP</title>
</head>
<body>
    <h1>Create Account</h1>

    <?php if ($error = getFlash('error')): ?>
        <div class="error"><?= $error ?></div>
    <?php endif; ?>

    <form method="POST">
        <?= csrfField() ?>
        
        <div>
            <label>Name:</label>
            <input type="text" name="name" value="<?= old('name') ?>" required>
            <?php if (isset($errors['name'])): ?>
                <span class="error"><?= $errors['name'][0] ?></span>
            <?php endif; ?>
        </div>

        <div>
            <label>Email:</label>
            <input type="email" name="email" value="<?= old('email') ?>" required>
            <?php if (isset($errors['email'])): ?>
                <span class="error"><?= $errors['email'][0] ?></span>
            <?php endif; ?>
        </div>

        <div>
            <label>Password:</label>
            <input type="password" name="password" required>
            <?php if (isset($errors['password'])): ?>
                <span class="error"><?= $errors['password'][0] ?></span>
            <?php endif; ?>
        </div>

        <div>
            <label>Confirm Password:</label>
            <input type="password" name="password_confirmation" required>
        </div>

        <div>
            <label>
                <input type="checkbox" name="terms" value="yes">
                I agree to the terms and conditions
            </label>
            <?php if (isset($errors['terms'])): ?>
                <span class="error"><?= $errors['terms'][0] ?></span>
            <?php endif; ?>
        </div>

        <button type="submit">Create Account</button>
        <a href="/login">Already have an account?</a>
    </form>
</body>
</html>

Login Page

<?php
// app/login/page.apna.php

use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;

function POST(Request $request): Response
{
    $email = $request->input('email');

    // Check if account is locked
    if (auth()->isAccountLocked($email)) {
        return redirect('/login')->withError('Account locked. Please try again later.');
    }

    try {
        $validated = validate($request->all(), [
            'email' => 'required|email',
            'password' => 'required'
        ]);

        $remember = $request->input('remember') === 'on';

        if (auth()->attempt($validated, $remember)) {
            auth()->clearLoginAttempts($email);
            console_success('User logged in', ['email' => $email]);
            return redirect('/dashboard')->withSuccess('Welcome back!');
        } else {
            auth()->incrementLoginAttempts($email);
            $remaining = 5 - auth()->getLoginAttempts($email);
            
            return redirect('/login')
                ->withError("Invalid credentials. {$remaining} attempts remaining.")
                ->withInput(['email' => $email]);
        }

    } catch (ValidationException $e) {
        return redirect('/login')->withErrors($e->errors())->withInput();
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Login - ApnaPHP</title>
</head>
<body>
    <h1>Login</h1>

    <?php if ($error = getFlash('error')): ?>
        <div class="error"><?= $error ?></div>
    <?php endif; ?>

    <form method="POST">
        <?= csrfField() ?>
        
        <div>
            <label>Email:</label>
            <input type="email" name="email" value="<?= old('email') ?>" required>
        </div>

        <div>
            <label>Password:</label>
            <input type="password" name="password" required>
        </div>

        <div>
            <label>
                <input type="checkbox" name="remember">
                Remember Me
            </label>
        </div>

        <button type="submit">Login</button>
        <a href="/register">Create an account</a>
        <a href="/forgot-password">Forgot password?</a>
    </form>
</body>
</html>

Protected Dashboard

<?php
// app/dashboard/middleware.apna.php

requireAuth(); // Automatically redirects if not logged in
?>
<?php
// app/dashboard/page.apna.php

metadata([
    'title' => 'Dashboard - ' . userName(),
    'robots' => 'noindex, nofollow' // Don't index private pages
]);

$user = user();
?>

<!DOCTYPE html>
<html>
<head>
    <title>Dashboard</title>
</head>
<body>
    <h1>Welcome, <?= e(userName()) ?>!</h1>
    
    <div>
        <p><strong>Email:</strong> <?= e(userEmail()) ?></p>
        <p><strong>Role:</strong> <?= e(userRole()) ?></p>
        <p><strong>User ID:</strong> <?= userId() ?></p>
    </div>

    <?php if (isAdmin()): ?>
        <div class="admin-section">
            <h2>Admin Panel</h2>
            <a href="/admin/users">Manage Users</a>
        </div>
    <?php endif; ?>

    <a href="/logout">Logout</a>
</body>
</html>

10. Best Practices

  • Always Hash Passwords: Never store plain text passwords. Use hashPassword().
  • Use CSRF Protection: Always include CSRF tokens in forms.
  • Implement Rate Limiting: Prevent brute force attacks with rate limiting.
  • Account Lockout: Lock accounts after multiple failed login attempts.
  • Remember Me Securely: Use secure cookies with proper expiry times.
  • Validate Input: Always validate user input before processing.
  • Log Authentication Events: Log logins, logouts, and failed attempts for security auditing.
  • Use HTTPS: Always use HTTPS in production to protect credentials.
  • Session Security: Use secure session settings and regenerate session IDs on login.
  • Password Requirements: Enforce strong password requirements (min 8 chars, complexity).

ApnaPHP's authentication system provides a solid foundation for securing your application while remaining simple and flexible.