Security Best Practices
ServITech Backend API implements multiple layers of security to protect user data and prevent unauthorized access. This guide covers the security features and best practices used in the project.
Authentication System
JWT (JSON Web Tokens)
ServITech uses JWT authentication for stateless, secure API access. JWT tokens are issued upon successful login and must be included in subsequent requests.
How JWT Works
User Login
User sends credentials to /api/auth/login:POST /api/auth/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "secure_password"
}
Token Generation
Server validates credentials and generates a JWT token:{
"message": "Usuario autenticado exitosamente",
"data": {
"user": { ... },
"token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"expires_in": 3600
}
}
Authenticated Requests
Client includes token in Authorization header:GET /api/user/profile
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
Token Validation
Server validates token on each request and grants/denies access.
JWT Configuration
JWT settings are configured in config/jwt.php:
return [
'secret' => env('JWT_SECRET'),
'ttl' => (int) env('JWT_TTL', 60), // Token lifetime: 60 minutes
'refresh_ttl' => (int) env('JWT_REFRESH_TTL', 20160), // 2 weeks
'algo' => env('JWT_ALGO', 'HS256'), // HMAC SHA-256 algorithm
'blacklist_enabled' => true, // Token invalidation on logout
];
Default token expiration is 60 minutes. Adjust JWT_TTL in .env based on your security requirements.
Generating JWT Secret
During installation, generate a secure JWT secret:
This adds JWT_SECRET to your .env file:
JWT_SECRET=your_generated_secret_key_here
Never commit your JWT_SECRET to version control. Keep it secure and unique per environment.
Token Blacklisting
ServITech implements token blacklisting to invalidate tokens on logout:
app/Http/Controllers/Auth/AuthController.php
public function logout(): JsonResponse
{
if (!auth()->check()) {
return ApiResponse::error(
message: __('messages.user.already_logged_out'),
status: Response::HTTP_UNAUTHORIZED
);
}
auth()->logout(); // Adds token to blacklist
return ApiResponse::success(
message: __('messages.user.logged_out'),
status: Response::HTTP_OK
);
}
Blacklisted tokens cannot be used for authentication, even if not expired.
Password Security
Hashing
Passwords are hashed using bcrypt with 12 rounds:
$user->password = bcrypt($password);
Laravel automatically hashes passwords when creating users if using the User model’s $casts or mutators.
Password Reset Flow
Request Reset
User requests password reset: Email Sent
System generates a secure token and emails reset link:$status = Password::sendResetLink(
$request->only('email')
);
Reset Password
User clicks link and submits new password:PUT /api/auth/reset-password
{
"token": "reset_token",
"email": "[email protected]",
"password": "new_password",
"password_confirmation": "new_password"
}
Password Updated
System validates token, updates password, and invalidates token:$user->forceFill([
'password' => bcrypt($password)
])->setRememberToken(Str::random(60));
Token expiration: 60 minutes (configurable in config/auth.php):
'passwords' => [
'users' => [
'expire' => 60, // Token expires in 60 minutes
'throttle' => 60, // Limit requests to one per 60 seconds
],
],
Authorization & Role-Based Access Control
Roles System
ServITech uses Spatie Laravel Permission for role-based access control.
Available Roles
Defined in app/Enums/UserRoles.php:
enum UserRoles: string
{
case USER = 'user';
case ADMIN = 'admin';
}
Role Assignment
New users are assigned the USER role by default:
app/Http/Controllers/Auth/AuthController.php
public function register(RegisterUserRequest $request): JsonResponse
{
$user = User::create($request->validated());
$user->assignRole(UserRoles::USER); // Assign USER role
return ApiResponse::success(
message: __('messages.user.registered'),
status: Response::HTTP_CREATED,
data: ['user' => UserResource::make($user)]
);
}
Protecting Routes by Role
Controller-Level Protection
Protect specific controller actions:
app/Http/Controllers/ArticleController.php
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class ArticleController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return [
new Middleware(
middleware: ['auth:api', 'role:' . UserRoles::ADMIN->value],
only: ['update', 'destroy', 'store']
),
];
}
}
This ensures only authenticated admin users can create, update, or delete articles.
Route-Level Protection
Protect route groups in routes/api.php:
// Protected Routes (authentication required)
Route::middleware('auth:api')->group(function () {
// Support requests - all authenticated users
Route::prefix('support-request')->group(function () {
Route::get('', [SupportRequestController::class, 'index']);
Route::post('', [SupportRequestController::class, 'store']);
});
// Admin-only routes (role required)
Route::middleware('role:' . UserRoles::ADMIN->value)->group(function () {
Route::prefix('repair-request')->group(function () {
Route::get('', [RepairRequestController::class, 'index']);
Route::post('', [RepairRequestController::class, 'store']);
});
});
});
Always apply auth:api middleware before role middleware. Role checks require an authenticated user.
Permission Caching
Spatie Permission caches roles and permissions for 24 hours:
'cache' => [
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
'key' => 'spatie.permission.cache',
'store' => 'default',
],
Clear cache after modifying roles/permissions:
php artisan cache:forget spatie.permission.cache
All input is validated using Form Request classes:
app/Http/Requests/Auth/LoginUserRequest.php
class LoginUserRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => 'required|email|exists:users,email',
'password' => 'required|string|min:8',
];
}
}
Benefits:
- Automatic validation before controller methods
- Prevents SQL injection
- Type coercion and sanitization
- Custom error messages
Validation Rules
Common security-focused validation rules:
// Email validation
'email' => 'required|email|unique:users,email'
// Password strength
'password' => [
'required',
'string',
'min:8',
'confirmed',
Password::min(8)
->mixedCase()
->numbers()
->symbols()
]
// Sanitize strings
'name' => 'required|string|max:255'
// Integer validation (prevents injection)
'article_id' => 'required|integer|exists:articles,id'
Always validate on the server side, even if client-side validation exists.
Protection Against Common Attacks
SQL Injection Prevention
Laravel’s Eloquent ORM and Query Builder use prepared statements:
// Safe - uses parameter binding
$users = User::where('email', $email)->first();
// Safe - prepared statement
$articles = DB::table('articles')
->where('category', $category)
->get();
// UNSAFE - never use raw queries with user input
// DB::select("SELECT * FROM users WHERE email = '$email'");
Avoid raw SQL queries. If necessary, use parameter binding:DB::select('SELECT * FROM users WHERE email = ?', [$email]);
Cross-Site Scripting (XSS)
Laravel automatically escapes output in Blade templates. For API responses:
// JSON responses are automatically encoded
return response()->json([
'message' => $userInput // Automatically escaped
]);
For HTML content (if needed):
use Illuminate\Support\Facades\Validator;
'content' => 'required|string|max:1000', // Limit length
// Strip tags if needed
$clean = strip_tags($request->content);
Cross-Site Request Forgery (CSRF)
For API endpoints, CSRF protection is disabled since JWT tokens are used:
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'api/*', // Exclude API routes from CSRF
]);
})
JWT tokens in the Authorization header provide CSRF protection.
For web routes using sessions/cookies, always enable CSRF protection.
Mass Assignment Protection
Protect models from mass assignment vulnerabilities:
class User extends Authenticatable
{
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
}
This prevents attackers from modifying unintended fields:
// Safe - only $fillable fields can be set
User::create($request->all());
// Attack prevented: 'is_admin' => true would be ignored
Rate Limiting
Laravel provides built-in rate limiting to prevent abuse.
Global API Rate Limiting
Configure in app/Http/Kernel.php or bootstrap/app.php:
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
This limits:
- Authenticated users: 60 requests per minute per user
- Anonymous users: 60 requests per minute per IP
Custom Rate Limiting
For sensitive endpoints like login:
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->email);
});
Apply to routes:
Route::post('login', [AuthController::class, 'login'])
->middleware('throttle:login');
Always rate-limit authentication endpoints to prevent brute-force attacks.
Environment Variables
Secure Configuration
All sensitive configuration is stored in .env (never committed):
APP_KEY=base64:generated_key_here
JWT_SECRET=your_jwt_secret
DB_CONNECTION=mysql
DB_PASSWORD=secure_password
MAIL_PASSWORD=mail_password
Environment Validation
Critical variables are validated at startup (recommended):
// Check required environment variables
if (!env('JWT_SECRET')) {
throw new RuntimeException('JWT_SECRET not set');
}
Never commit .env files. Always use .env.example as a template.
HTTPS in Production
Always use HTTPS in production to encrypt data in transit.
Force HTTPS
In app/Providers/AppServiceProvider.php:
public function boot(): void
{
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
}
Redirect HTTP to HTTPS
Configure at the web server level (Apache/Nginx) or use middleware:
Route::middleware('https')->group(function () {
// All routes
});
Add security headers to all responses:
// In middleware or global response
return $response
->header('X-Content-Type-Options', 'nosniff')
->header('X-Frame-Options', 'DENY')
->header('X-XSS-Protection', '1; mode=block')
->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
Logging & Monitoring
Security Event Logging
Log security-relevant events:
use Illuminate\Support\Facades\Log;
// Failed login attempts
Log::warning('Failed login attempt', [
'email' => $request->email,
'ip' => $request->ip(),
]);
// Successful logins
Log::info('User logged in', [
'user_id' => auth()->id(),
'ip' => $request->ip(),
]);
// Permission violations
Log::warning('Unauthorized access attempt', [
'user_id' => auth()->id(),
'route' => $request->path(),
]);
Monitor Logs
Regularly review logs for suspicious activity:
tail -f storage/logs/laravel.log
Security Checklist
Next Steps