ra-devs/jwt-auth

Universal JWT auth for Laravel (tymon/jwt-auth + ra-devs/api-json-response)

Installs: 177

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/ra-devs/jwt-auth

v1.4.4 2026-01-13 10:07 UTC

This package is auto-updated.

Last update: 2026-01-13 10:08:13 UTC


README

Production-ready JWT authentication package for Laravel

Built on top of tymon/jwt-auth + ra-devs/api-json-response

Laravel PHP License

โœจ Features

Feature Description
๐Ÿ”‘ Authentication Complete auth flow: Login, Register, Logout, Token Refresh
๐Ÿ”’ Password Reset Secure 8-character alphanumeric codes (Aโ€“Z, 2โ€“9) via email
๐Ÿ›ก๏ธ Security Rate limiting, event logging, custom exceptions
๐Ÿ“Š Monitoring Security event logging for audit trails
๐ŸŽฏ Error Handling Structured error responses with error codes
โšก Performance Database indexes, optimized queries
๐Ÿงช Testing Comprehensive test suite with >80% coverage
๐Ÿ“š Documentation OpenAPI spec, examples, FAQ
๐Ÿ”ง Customizable User model, Resources, Repositories, Notifications
๐Ÿ“ฆ Publishable Config, migrations, routes, views

๐Ÿ“‹ Table of Contents

๐Ÿš€ Installation

Step 1: Install Dependencies

Install the required packages via Composer:

composer require tymon/jwt-auth "^2.0"
composer require ra-devs/api-json-response "^1.0"
composer require ra-devs/jwt-auth:dev-main

Step 2: Publish Package Resources

Publish the configuration, migrations, and views:

# Publish configuration
php artisan vendor:publish --provider="RaDevs\JwtAuth\Providers\JwtAuthServiceProvider" --tag=ra-jwt-auth-config

# Publish migrations
php artisan vendor:publish --provider="RaDevs\JwtAuth\Providers\JwtAuthServiceProvider" --tag=ra-jwt-auth-migrations

# Publish views (optional - for email templates)
php artisan vendor:publish --provider="RaDevs\JwtAuth\Providers\JwtAuthServiceProvider" --tag=ra-jwt-auth-views

Step 3: Configure JWT Auth

Publish and configure the base JWT package:

# Publish JWT config
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

# Generate JWT secret key
php artisan jwt:secret

Step 4: Configure Auth Guard

Update your config/auth.php file:

<?php

return [
    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],

    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
    ],
];

Step 5: Run Migrations

Run the database migrations:

php artisan migrate

This will create the following tables:

  • password_reset_codes - Stores password reset codes
  • Adds refresh_token column to users table

๐ŸŽฏ Quick Start

1. Register a New User

curl -X POST http://your-app.test/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "name": "John",
    "last_name": "Doe",
    "phone": "+1234567890",
    "password": "SecurePass123!",
    "password_confirmation": "SecurePass123!"
  }'

Response:

{
  "success": true,
  "status": "success",
  "message": "User registered successfully",
  "data": {
    "user": {
      "id": 1,
      "name": "John",
      "email": "user@example.com"
    }
  }
}

2. Login

curl -X POST http://your-app.test/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "SecurePass123!"
  }'

Response:

{
  "success": true,
  "status": "success",
  "message": "The user has been successfully logged in",
  "data": {
    "user": {
      "id": 1,
      "name": "John",
      "email": "user@example.com"
    },
    "token": {
      "access_token": "eyJ0eXAiOiJKV1QiLCJhbGci...",
      "token_type": "bearer",
      "expires_in": 3600
    }
  }
}

3. Access Protected Route

curl -X GET http://your-app.test/api/auth/me \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGci..." \
  -H "Accept: application/json"

4. Refresh Token

The refresh token is automatically sent as an HTTP-only cookie. To refresh:

curl -X POST http://your-app.test/api/auth/refresh \
  -H "Cookie: refresh_token=your_refresh_token" \
  -H "Accept: application/json"

๐Ÿ“ก API Endpoints

All endpoints are prefixed with /api/auth by default (configurable).

Method Endpoint Description Auth Required Rate Limit
POST /login Authenticate user and get tokens โŒ 5/min
POST /register Register a new user โŒ 3/min
GET /me Get current authenticated user โœ… -
POST /logout Logout and clear refresh token โœ… -
POST /refresh Refresh access token via cookie โœ… 10/min
POST /forgot-password Request password reset code โŒ 3/min
POST /reset-password Reset password using code โŒ 3/min

Request/Response Examples

POST /api/auth/login

Request:

{
  "email": "user@example.com",
  "password": "password123"
}

Success Response (200):

{
  "success": true,
  "status": "success",
  "message": "The user has been successfully logged in",
  "data": {
    "user": {
      "id": 1,
      "name": "John Doe",
      "email": "user@example.com"
    },
    "token": {
      "access_token": "eyJ0eXAiOiJKV1QiLCJhbGci...",
      "token_type": "bearer",
      "expires_in": 3600
    }
  }
}

Error Response (401):

{
  "success": false,
  "status": "error",
  "message": "Invalid credentials",
  "data": {
    "error_code": "INVALID_CREDENTIALS"
  }
}
POST /api/auth/register

Request:

{
  "email": "newuser@example.com",
  "name": "Jane",
  "last_name": "Smith",
  "phone": "+1234567890",
  "password": "SecurePass123!",
  "password_confirmation": "SecurePass123!"
}

Success Response (201):

{
  "success": true,
  "status": "success",
  "message": "User registered successfully",
  "data": {
    "user": {
      "id": 2,
      "name": "Jane",
      "email": "newuser@example.com"
    }
  }
}
POST /api/auth/forgot-password

Request:

{
  "email": "user@example.com"
}

Success Response (200):

{
  "success": true,
  "status": "success",
  "message": "Password reset code has been sent to your email"
}

Note: For security, the response is the same whether the email exists or not.

POST /api/auth/reset-password

Request:

{
  "email": "user@example.com",
  "code": "ABCD1234",
  "password": "NewSecurePass123!",
  "password_confirmation": "NewSecurePass123!"
}

Success Response (200):

{
  "success": true,
  "status": "success",
  "message": "Password has been reset successfully"
}

Error Response (400):

{
  "success": false,
  "status": "error",
  "message": "Invalid or expired reset code",
  "data": {
    "error_code": "PASSWORD_RESET_CODE_INVALID"
  }
}

โš™๏ธ Configuration

After publishing, edit config/ra-jwt-auth.php to customize the package behavior.

Route Configuration

'route' => [
    'prefix' => 'api/auth',        // API route prefix
    'middleware' => ['api'],        // Middleware groups
],

Refresh Token Cookie

'refresh_cookie' => [
    'name' => env('RA_JWT_AUTH_REFRESH_COOKIE_NAME', 'refresh_token'),
    'minutes' => env('RA_JWT_AUTH_REFRESH_COOKIE_MINUTES', 60 * 24), // 1 day
    'secure' => env('RA_JWT_AUTH_REFRESH_COOKIE_SECURE', null),     // null = auto-detect
    'http_only' => env('RA_JWT_AUTH_REFRESH_COOKIE_HTTP_ONLY', true),
    'same_site' => env('RA_JWT_AUTH_REFRESH_COOKIE_SAME_SITE', null), // 'lax'|'strict'|'none'
    'path' => env('RA_JWT_AUTH_REFRESH_COOKIE_PATH', '/'),
    'domain' => env('RA_JWT_AUTH_REFRESH_COOKIE_DOMAIN', null),
],

Custom Classes

Override default implementations:

'classes' => [
    'user_model' => App\Models\User::class,
    'user_resource' => App\Http\Resources\UserResource::class,
    'auth_repository_interface' => RaDevs\JwtAuth\Repositories\Contracts\IAuthRepository::class,
    'auth_repository' => App\Repositories\CustomAuthRepository::class,
    'user_repository_interface' => RaDevs\JwtAuth\Repositories\Contracts\IUserRepository::class,
    'user_repository' => App\Repositories\CustomUserRepository::class,
    'password_reset_service' => RaDevs\JwtAuth\Services\PasswordResetCodeService::class,
    'notification' => App\Notifications\CustomResetPasswordNotification::class,
],

Registration Fields

Customize registration validation rules:

'registration' => [
    'fields' => [
        'email' => 'required|email:rfc,dns|unique:users,email',
        'name' => 'required|string|max:255',
        'last_name' => 'required|string|max:255',
        'phone' => 'required|string|regex:/^\+\d{10,15}$/',
        'password' => 'required|confirmed|min:8',
        'password_confirmation' => 'required_with:password',
    ],
    'exclude_from_create' => [
        'password_confirmation',
    ],
],

Password Reset Settings

'password_reset' => [
    'code_length' => env('RA_JWT_AUTH_PASSWORD_RESET_CODE_LENGTH', 8),
    'code_alphabet' => env('RA_JWT_AUTH_PASSWORD_RESET_CODE_ALPHABET', 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'),
    'max_attempts' => env('RA_JWT_AUTH_PASSWORD_RESET_MAX_ATTEMPTS', 5),
    'rate_limit_seconds' => env('RA_JWT_AUTH_PASSWORD_RESET_RATE_LIMIT_SECONDS', 60),
],

Rate Limits

Configure rate limiting per endpoint:

'rate_limits' => [
    'login' => env('RA_JWT_AUTH_RATE_LIMIT_LOGIN', 5),
    'register' => env('RA_JWT_AUTH_RATE_LIMIT_REGISTER', 3),
    'forgot_password' => env('RA_JWT_AUTH_RATE_LIMIT_FORGOT_PASSWORD', 3),
    'reset_password' => env('RA_JWT_AUTH_RATE_LIMIT_RESET_PASSWORD', 3),
    'refresh' => env('RA_JWT_AUTH_RATE_LIMIT_REFRESH', 10),
],

๐Ÿ’ก Usage Examples

Custom User Resource

Create a custom resource to control API responses:

<?php
// app/Http/Resources/UserResource.php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'avatar' => $this->avatar_url,
            'created_at' => $this->created_at->toIso8601String(),
        ];
    }
}

Then update config/ra-jwt-auth.php:

'classes' => [
    'user_resource' => App\Http\Resources\UserResource::class,
],

Custom Repository

Override authentication logic:

<?php
// app/Repositories/CustomAuthRepository.php

namespace App\Repositories;

use RaDevs\JwtAuth\Repositories\AuthRepository;
use RaDevs\JwtAuth\Repositories\Contracts\IAuthRepository;

class CustomAuthRepository extends AuthRepository implements IAuthRepository
{
    public function login(array $credentials): array
    {
        // Add custom logic before login
        // e.g., check user status, log attempts, etc.
        
        $result = parent::login($credentials);
        
        // Add custom logic after login
        // e.g., update last login, send notification, etc.
        
        return $result;
    }
}

Custom Password Reset Notification

Create a custom email template:

<?php
// app/Notifications/CustomResetPasswordNotification.php

namespace App\Notifications;

use RaDevs\JwtAuth\Notifications\ApiResetPasswordCodeNotification;

class CustomResetPasswordNotification extends ApiResetPasswordCodeNotification
{
    public function toMail($notifiable)
    {
        return (new \Illuminate\Notifications\Messages\MailMessage)
            ->subject('Reset Your Password')
            ->view('emails.password-reset', [
                'code' => $this->code,
                'user' => $notifiable,
            ]);
    }
}

๐Ÿ›ก๏ธ Security Features

Rate Limiting

All endpoints are protected with configurable rate limits to prevent brute force attacks:

  • Login: 5 attempts per minute
  • Register: 3 attempts per minute
  • Password Reset: 3 attempts per minute
  • Token Refresh: 10 attempts per minute

When rate limit is exceeded, the API returns:

{
  "success": false,
  "status": "error",
  "message": "Too many attempts. Please try again later.",
  "data": {
    "error_code": "RATE_LIMIT_EXCEEDED",
    "retry_after": 60
  }
}

Security Event Logging

All authentication events are automatically logged with IP address and user agent:

  • โœ… User login (success/failure)
  • โœ… User registration
  • โœ… User logout
  • โœ… Password reset requests
  • โœ… Password reset completion

Listen to events in your EventServiceProvider:

<?php
// app/Providers/EventServiceProvider.php

use RaDevs\JwtAuth\Events\UserLoggedIn;
use RaDevs\JwtAuth\Events\UserLoginFailed;

protected $listen = [
    UserLoggedIn::class => [
        SendLoginNotification::class,
        UpdateLastLoginTimestamp::class,
    ],
    UserLoginFailed::class => [
        LogFailedAttempt::class,
    ],
];

Error Codes

Structured error responses with error codes for programmatic handling:

Error Code Description
INVALID_CREDENTIALS Wrong email/password combination
USER_NOT_FOUND User does not exist
INVALID_TOKEN Token is invalid or expired
PASSWORD_RESET_CODE_EXPIRED Reset code has expired
PASSWORD_RESET_CODE_INVALID Invalid reset code
PASSWORD_RESET_TOO_MANY_ATTEMPTS Too many code verification attempts
RATE_LIMIT_EXCEEDED Too many requests

Password Reset Security

  • 8-character codes using alphabet without confusing characters (no I, O, 0, 1)
  • Time-limited codes (configurable expiration)
  • Attempt limiting (max 5 attempts by default)
  • Rate limiting on code requests (1 request per minute)

๐ŸŒ Frontend Integration

Vue.js / React

See detailed integration examples in docs/EXAMPLES.md for:

  • Vue.js with Pinia
  • React with Context API
  • Axios interceptors
  • Token refresh strategies
  • Error handling

Quick Example (Axios)

import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.example.com/api',
  withCredentials: true, // Important for refresh token cookies
});

// Add access token to requests
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Handle token refresh on 401
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401 && !error.config._retry) {
      error.config._retry = true;
      
      try {
        const { data } = await axios.post('/api/auth/refresh', {}, { withCredentials: true });
        localStorage.setItem('access_token', data.data.token.access_token);
        error.config.headers.Authorization = `Bearer ${data.data.token.access_token}`;
        return api(error.config);
      } catch {
        localStorage.removeItem('access_token');
        window.location.href = '/login';
      }
    }
    return Promise.reject(error);
  }
);

๐Ÿงช Testing

Run Tests

# Run all tests
composer test

# Run without coverage
vendor/bin/phpunit --no-coverage

# Run specific test file
vendor/bin/phpunit tests/Feature/AuthControllerTest.php

# Run with coverage report
composer test-coverage

Test Coverage

The package includes a comprehensive test suite:

  • โœ… Unit tests for services (7/7 passing)
  • โœ… Feature tests for API endpoints (14/14 passing)
  • โœ… Rate limiting tests (2/2 passing)
  • โœ… Password reset service tests (7/7 passing)

Current Status: 21/21 passing (100%)

โœ… All tests are passing successfully! See Testing Guide for details.

๐Ÿ“š Documentation

๐Ÿ”ง Customization

Override User Model

Ensure your User model implements JWTSubject:

<?php
// app/Models/User.php

use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return [];
    }
}

Custom Validation Rules

Extend the request classes to add custom validation:

<?php
// app/Http/Requests/CustomRegisterRequest.php

namespace App\Http\Requests;

use RaDevs\JwtAuth\Http\Requests\Auth\RegisterRequest;

class CustomRegisterRequest extends RegisterRequest
{
    public function rules(): array
    {
        $rules = parent::rules();
        
        // Add custom rules
        $rules['email'] .= '|unique:users,email,deleted_at,NULL';
        $rules['terms'] = 'required|accepted';
        
        return $rules;
    }
}

Custom Routes

Override routes by publishing and editing routes/api.php, or add middleware:

// In your RouteServiceProvider or routes file
Route::prefix('api/auth')
    ->middleware(['api', 'throttle:60,1'])
    ->group(function () {
        // Your custom routes
    });

๐Ÿšง Development

Local Development Setup

For local development, add the package as a path repository in composer.json:

{
  "repositories": [
    {
      "type": "path",
      "url": "../ra-devs-jwt-auth",
      "options": {
        "symlink": true
      }
    }
  ]
}

Then require it:

composer require ra-devs/jwt-auth:"*@dev"

Project Structure

src/
โ”œโ”€โ”€ Events/              # Event classes
โ”œโ”€โ”€ Exceptions/          # Custom exceptions
โ”œโ”€โ”€ Http/
โ”‚   โ”œโ”€โ”€ Controllers/     # Controllers
โ”‚   โ”œโ”€โ”€ Requests/        # Form requests
โ”‚   โ””โ”€โ”€ Resources/       # API resources
โ”œโ”€โ”€ Listeners/           # Event listeners
โ”œโ”€โ”€ Models/              # Eloquent models
โ”œโ”€โ”€ Providers/           # Service providers
โ”œโ”€โ”€ Repositories/        # Repository classes
โ”‚   โ””โ”€โ”€ Contracts/       # Repository interfaces
โ””โ”€โ”€ Services/            # Business logic services

๐Ÿค Contributing

Contributions are welcome! Please read our Contributing Guidelines before submitting a pull request.

Quick Contribution Steps

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add/update tests
  5. Update documentation
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

๐Ÿ“„ License

MIT ยฉ RA Devs

Made with โค๏ธ by RA Devs

Report Bug ยท Request Feature ยท Documentation