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
Requires
- php: >=8.1
- illuminate/database: >=10.0
- illuminate/http: >=10.0
- illuminate/mail: >=10.0
- illuminate/notifications: >=10.0
- illuminate/support: >=10.0
- illuminate/validation: >=10.0
- ra-devs/api-json-response: ^1.0
- tymon/jwt-auth: ^2.0
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.5
README
Production-ready JWT authentication package for Laravel
Built on top of tymon/jwt-auth + ra-devs/api-json-response
โจ 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
- Quick Start
- API Endpoints
- Configuration
- Usage Examples
- Customization
- Security Features
- Frontend Integration
- Testing
- Documentation
- Contributing
๐ 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_tokencolumn touserstable
๐ฏ 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
- ๐ API Documentation (OpenAPI) - Complete API specification
- ๐ก Integration Examples - Vue.js, React, Axios examples
- โ Frequently Asked Questions - Common questions and solutions
- ๐ค Contributing Guidelines - How to contribute
- ๐ Changelog - Version history
๐ง 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
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add/update tests
- Update documentation
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
๐ License
MIT ยฉ RA Devs
Made with โค๏ธ by RA Devs