Protected Routes

15 minutes INTERMEDIATE

Protect your API endpoints with authentication and authorization. Learn how to use auth() method to verify JWT tokens and implement role-based access control.

What You'll Learn

Understanding auth()

What is the auth() method and how it works

Authentication

Protect routes with auth() - check for valid token

Authorization

Protect routes with auth([roles]) - check for specific roles

Video Coming Soon...

What is Protected Routes?

Protected routes are API endpoints that require authentication or authorization before allowing access. GEMVC provides a simple auth() method in the Request object to protect your endpoints.

  • Authentication - Verifies that the user has a valid JWT token (auth())
  • Authorization - Verifies that the user has specific roles (auth(['admin', 'salesManager']))
  • Automatic Response - Returns 401 (Unauthorized) or 403 (Forbidden) automatically if check fails
  • Multi-Role Support - Can check for multiple roles in a single call

Key Point: The auth() method is available in all API Service classes via $this->request->auth(). It automatically validates JWT tokens and checks roles.

1

Understanding auth() Method

The auth() Method

The auth() method is available in the Request object and can be called in two ways:

1. Authentication Only (No Arguments)

$this->request->auth() - Checks if the user has a valid JWT token.

  • ✓ Verifies JWT token signature
  • ✓ Checks token expiration
  • ✓ Validates user ID
  • ✓ Returns true if valid, false otherwise
  • ✓ Returns 401 (Unauthorized) if invalid

2. Authorization with Roles (Array Argument)

$this->request->auth(['admin', 'salesManager']) - Checks if the user has a valid token AND has one of the specified roles.

  • ✓ First performs authentication check (valid token)
  • ✓ Then checks if user has any of the specified roles
  • ✓ Supports multiple roles (OR logic - user needs at least one)
  • ✓ Returns true if authenticated and authorized
  • ✓ Returns 401 (Unauthorized) if token invalid
  • ✓ Returns 403 (Forbidden) if token valid but role missing

How Roles Work:

Roles are stored in the JWT token payload as a comma-separated string (e.g., "admin,user"). When you call auth(['admin', 'salesManager']), GEMVC checks if the user's token contains at least one of these roles.

Example: If token has role: "admin,user" and you call auth(['admin', 'salesManager']), it will return true because the user has the "admin" role.

2

Basic Authentication (auth() without arguments)

Protecting Routes with Authentication

Use auth() without arguments to require a valid JWT token. This is useful for endpoints that any authenticated user can access.

app/api/Product.php - Basic Authentication
<?php
namespace App\Api;

use App\Controller\ProductController;
use Gemvc\Core\ApiService;
use Gemvc\Http\Request;
use Gemvc\Http\JsonResponse;

class Product extends ApiService
{
    public function __construct(Request $request)
    {
        parent::__construct($request);
    }

    // Read - Requires authentication (any logged-in user)
    public function read(): JsonResponse
    {
        // Check if user has valid JWT token
        if (!$this->request->auth()) {
            return $this->request->returnResponse(); // Returns 401 Unauthorized
        }
        
        // User is authenticated - proceed with request
        return (new ProductController($this->request))->read();
    }

    // Update - Requires authentication (any logged-in user)
    public function update(): JsonResponse
    {
        // Check authentication first
        if (!$this->request->auth()) {
            return $this->request->returnResponse(); // Returns 401 Unauthorized
        }
        
        // Validate schema
        if(!$this->request->definePostSchema([
            'id' => 'int',
            '?name' => 'string',
            '?price' => 'float'
        ])) {
            return $this->request->returnResponse();
        }
        
        // User is authenticated - proceed
        return (new ProductController($this->request))->update();
    }
}

What Happens:

  • ✓ If token is valid → Returns true, request continues
  • ✓ If token is missing/invalid/expired → Returns false
  • returnResponse() automatically returns 401 Unauthorized
  • ✓ No need to manually check response codes
3

Role-Based Authorization (auth() with roles)

Protecting Routes with Specific Roles

Use auth(['role1', 'role2']) with an array of roles to restrict access to users with specific roles. This is useful for admin-only endpoints or role-specific operations.

app/api/Product.php - Role-Based Authorization
<?php
namespace App\Api;

use App\Controller\ProductController;
use Gemvc\Core\ApiService;
use Gemvc\Http\Request;
use Gemvc\Http\JsonResponse;

class Product extends ApiService
{
    public function __construct(Request $request)
    {
        parent::__construct($request);
    }

    // Create - Only admin and salesManager can create products
    public function create(): JsonResponse
    {
        // Check if user has valid token AND has 'admin' or 'salesManager' role
        if (!$this->request->auth(['admin', 'salesManager'])) {
            return $this->request->returnResponse(); 
            // Returns 401 if no token, 403 if token valid but role missing
        }
        
        // Validate schema
        if(!$this->request->definePostSchema([
            'name' => 'string',
            'price' => 'float',
            '?description' => 'string'
        ])) {
            return $this->request->returnResponse();
        }
        
        // User is authenticated and authorized - proceed
        return (new ProductController($this->request))->create();
    }

    // Delete - Only admin can delete products
    public function delete(): JsonResponse
    {
        // Check if user has 'admin' role
        if (!$this->request->auth(['admin'])) {
            return $this->request->returnResponse(); // Returns 401 or 403
        }
        
        // Validate schema
        if(!$this->request->definePostSchema([
            'id' => 'int'
        ])) {
            return $this->request->returnResponse();
        }
        
        // User is authenticated and authorized - proceed
        return (new ProductController($this->request))->delete();
    }
}

How Role Checking Works:

  • auth(['admin', 'salesManager']) checks if user has at least one of these roles
  • ✓ If user has 'admin' role → Access granted
  • ✓ If user has 'salesManager' role → Access granted
  • ✓ If user has neither role → Returns 403 Forbidden
  • ✓ If token is invalid → Returns 401 Unauthorized

Response Codes:

  • 401 Unauthorized - Token is missing, invalid, or expired
  • 403 Forbidden - Token is valid but user doesn't have required role(s)
4

Complete Example: Product API

Full Product API with Different Access Levels

Here's a complete example showing different protection levels for different endpoints:

app/api/Product.php - Complete Example
<?php
namespace App\Api;

use App\Controller\ProductController;
use Gemvc\Core\ApiService;
use Gemvc\Http\Request;
use Gemvc\Http\JsonResponse;

class Product extends ApiService
{
    public function __construct(Request $request)
    {
        parent::__construct($request);
    }

    // Create - Only admin and salesManager
    public function create(): JsonResponse
    {
        if (!$this->request->auth(['admin', 'salesManager'])) {
            return $this->request->returnResponse();
        }
        
        if(!$this->request->definePostSchema([
            'name' => 'string',
            'price' => 'float',
            '?description' => 'string'
        ])) {
            return $this->request->returnResponse();
        }
        
        return (new ProductController($this->request))->create();
    }

    // Read - Any authenticated user
    public function read(): JsonResponse
    {
        if (!$this->request->auth()) {
            return $this->request->returnResponse();
        }
        
        if(!$this->request->defineGetSchema(['id' => 'int'])) {
            return $this->request->returnResponse();
        }
        
        $id = $this->request->intValueGet('id');
        if(!$id) {
            return $this->request->returnResponse();
        }
        
        $this->request->post['id'] = $id;
        return (new ProductController($this->request))->read();
    }

    // Update - Any authenticated user
    public function update(): JsonResponse
    {
        if (!$this->request->auth()) {
            return $this->request->returnResponse();
        }
        
        if(!$this->request->definePostSchema([
            'id' => 'int',
            '?name' => 'string',
            '?price' => 'float'
        ])) {
            return $this->request->returnResponse();
        }
        
        return (new ProductController($this->request))->update();
    }

    // Delete - Only admin
    public function delete(): JsonResponse
    {
        if (!$this->request->auth(['admin'])) {
            return $this->request->returnResponse();
        }
        
        if(!$this->request->definePostSchema(['id' => 'int'])) {
            return $this->request->returnResponse();
        }
        
        return (new ProductController($this->request))->delete();
    }

    // List - Public (no authentication required)
    public function list(): JsonResponse
    {
        $this->request->findable(['name' => 'string']);
        $this->request->sortable(['id', 'name', 'price']);
        return (new ProductController($this->request))->list();
    }
}

Access Control Summary:

  • create() - Requires 'admin' OR 'salesManager' role
  • read() - Requires any authenticated user
  • update() - Requires any authenticated user
  • delete() - Requires 'admin' role only
  • list() - Public (no authentication required)
5

Testing Protected Routes

How to Test Authentication and Authorization

When testing protected routes, you need to include the JWT token in the Authorization header:

Request with Token:

Terminal
curl -X POST http://localhost:9501/api/Product/create \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN_HERE" \
  -d '{
    "name": "New Product",
    "price": 99.99,
    "description": "Product description"
  }'

Response (Success - 200):

Success Response
{
    "response_code": 201,
    "message": "created",
    "count": 1,
    "service_message": "Product created successfully",
    "data": { ... }
}

Response (No Token - 401):

401 Unauthorized
{
    "response_code": 401,
    "message": "unauthorized",
    "count": 0,
    "service_message": "Invalid JWT token. Authentication failed",
    "data": null
}

Response (Wrong Role - 403):

403 Forbidden
{
    "response_code": 403,
    "message": "forbidden",
    "count": 0,
    "service_message": "Role user not allowed to perform this action",
    "data": null
}

Important Notes

  • auth() without arguments: Only checks if the user has a valid JWT token. Any authenticated user can access the endpoint.
  • auth(['role1', 'role2']): Checks for valid token AND verifies the user has at least one of the specified roles. Uses OR logic (user needs one role, not all).
  • Response Codes: 401 = Invalid/missing token, 403 = Valid token but missing required role(s).
  • Token Format: Client must send token in Authorization: Bearer TOKEN header format.
  • Role Storage: Roles are stored in JWT token payload as comma-separated string (e.g., "admin,user").

🔐 Protected Routes Complete!

Excellent! You've learned how to protect your API endpoints with authentication and role-based authorization. Your GEMVC application is now secure!