Skip to main content

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

1

User Login

User sends credentials to /api/auth/login:
POST /api/auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "secure_password"
}
2

Token Generation

Server validates credentials and generates a JWT token:
{
  "message": "Usuario autenticado exitosamente",
  "data": {
    "user": { ... },
    "token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
    "expires_in": 3600
  }
}
3

Authenticated Requests

Client includes token in Authorization header:
GET /api/user/profile
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
4

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:
php artisan 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:
config/hashing.php
BCRYPT_ROUNDS=12
$user->password = bcrypt($password);
Laravel automatically hashes passwords when creating users if using the User model’s $casts or mutators.

Password Reset Flow

1

Request Reset

User requests password reset:
POST /api/auth/reset-password
{
  "email": "[email protected]"
}
2

Email Sent

System generates a secure token and emails reset link:
$status = Password::sendResetLink(
    $request->only('email')
);
3

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"
}
4

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:
config/permission.php
'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

Input Validation

Form Requests

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:
bootstrap/app.php
->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:
app/Models/User.php
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:
routes/api.php
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):
.env
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
});

Security Headers

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

1

Environment Security

  • APP_ENV=production in production
  • APP_DEBUG=false in production
  • Strong APP_KEY generated
  • Strong JWT_SECRET generated
  • .env not in version control
2

Authentication

  • JWT tokens implemented
  • Token blacklisting enabled
  • Password hashing with bcrypt
  • Password reset tokens expire
  • Rate limiting on login
3

Authorization

  • Role-based access control configured
  • Admin routes protected
  • Middleware applied correctly
4

Input Validation

  • All inputs validated
  • Form Request classes used
  • SQL injection prevented
  • Mass assignment protected
5

Production

  • HTTPS enforced
  • Security headers configured
  • CORS properly configured
  • Error pages don’t leak info
  • Logs monitored

Next Steps

Build docs developers (and LLMs) love