The Trongate Security Module
The Trongate_security module provides a centralized way to handle authorization across your application using scenarios.
Instead of scattering security checks throughout your codebase, you call one method and let it route to the appropriate handler:
$this->trongate_security->make_sure_allowed('scenario_name');
This approach keeps your security logic organized, consistent, and easy to maintain.
What Are Scenarios?
A scenario is a named security context that describes what access control rules should apply.
Examples of scenarios:
'admin panel' - Only administrators can access
'members area' - Any logged-in member can access
'read comment' - Anyone can access (public)
'edit comment' - Only comment owner or admin can access
'premium content' - Only premium subscribers can access
Each scenario routes to a specific module that knows how to handle that particular type of access control.
It's for you - the developer - to decide how many scenarios your application is going to have. You also, as the developer, determine what names each scenario should be given.
How It Works
The Trongate_security module uses a switch statement to delegate scenarios to appropriate modules:
<?php
class Trongate_security extends Trongate {
public function __construct(?string $module_name = null) {
parent::__construct($module_name);
block_url($this->module_name);
}
public function make_sure_allowed(string $scenario = 'admin panel', array $params = []): mixed {
switch ($scenario) {
// case 'members area':
// $result = $this->members->make_sure_allowed($scenario, $params);
// return $result;
default:
// Default: admin panel access
$result = $this->trongate_administrators->make_sure_allowed();
return $result;
}
}
}
The commented-out case shows how to add custom scenarios. Uncomment and modify as needed for your application.
The Default Scenario
If you don't specify a scenario, it defaults to 'admin panel':
// These two calls are identical
$this->trongate_security->make_sure_allowed();
$this->trongate_security->make_sure_allowed('admin panel');
Both delegate to trongate_administrators->make_sure_allowed(), which:
- Checks for a valid token with user level 1 (admin)
- In dev mode: auto-generates a token for convenience
- In production: redirects to login if no valid token
- Returns the token string if successful
Return Values: Flexible by Design
The make_sure_allowed() method is intentionally open-ended. It can return:
- String - The validated token
- Boolean -
true for public access, false for denied
- Object - Complete user object with permissions
- Array - Custom data structure with multiple values
- Nothing - Can redirect or die instead of returning
The return type depends on what your scenario needs. You decide.
This flexibility means make_sure_allowed() can adapt to any authorization pattern your application requires.
Example: Discussion Forum
Let's build a hypothetical discussion forum accessible by the public, with special privileges for admin users and member users.
The Requirements
- Read comments - Open to anyone (no authentication)
- Create comments - Must be a logged-in member
- Edit/delete comments - Must be the comment owner OR an admin
Trongate_security.php
Add forum scenarios to the switch statement:
public function make_sure_allowed(string $scenario = 'admin panel', array $params = []): mixed {
switch ($scenario) {
case 'read comment':
// Public access - no authentication required
return true;
case 'create comment':
// Must be a member (user level 2)
$token = $this->trongate_tokens->attempt_get_valid_token(2);
if ($token === false) {
redirect('members/login');
}
return $token;
case 'edit comment':
case 'delete comment':
// Delegate to members module for ownership check
$result = $this->members->make_sure_allowed($scenario, $params);
return $result;
default:
// Admin panel access
$result = $this->trongate_administrators->make_sure_allowed();
return $result;
}
}
Members.php
The members module handles complex ownership checks:
public function make_sure_allowed(string $scenario = 'members area', array $params = []): mixed {
block_url('members/make_sure_allowed');
switch ($scenario) {
case 'edit comment':
case 'delete comment':
$comment_id = $params['comment_id'] ?? null;
if (!is_numeric($comment_id)) {
http_response_code(400);
die('Invalid comment ID');
}
// Get current user
$user = $this->trongate_tokens->get_user_obj();
if ($user === false) {
redirect('members/login');
}
// Fetch comment
$comment = $this->db->get_where($comment_id, 'comments');
if (!$comment) {
http_response_code(404);
die('Comment not found');
}
// Check ownership
if ($comment->trongate_user_id === $user->trongate_user_id) {
return $user->token; // Owner can edit/delete
}
// Check admin status
if ($user->user_level === 'admin') {
return $user->token; // Admin can edit/delete
}
// Access denied
http_response_code(403);
die('You do not have permission to ' . $scenario);
default:
return false;
}
}
The make_sure_allowed() method name, as shown above, uses block_url() to prevent URL invocation.
For more information about these topics check out:
Forum_comments.php (Controller)
Using the scenarios in your controller:
<?php
class Forum_comments extends Trongate {
public function view(int $comment_id): void {
// Anyone can read
$this->trongate_security->make_sure_allowed('read comment');
$data['comment'] = $this->db->get_where($comment_id, 'comments');
$data['view_file'] = 'view_comment';
$this->templates->public($data);
}
public function create(): void {
// Must be logged in
$token = $this->trongate_security->make_sure_allowed('create comment');
$data['view_file'] = 'create_comment';
$this->templates->members_area($data);
}
public function edit(int $comment_id): void {
// Check ownership or admin rights
$this->trongate_security->make_sure_allowed('edit comment', [
'comment_id' => $comment_id
]);
$data['comment'] = $this->db->get_where($comment_id, 'comments');
$data['view_file'] = 'edit_comment';
$this->templates->members_area($data);
}
public function delete(int $comment_id): void {
// Check ownership or admin rights
$this->trongate_security->make_sure_allowed('delete comment', [
'comment_id' => $comment_id
]);
$this->db->delete($comment_id, 'comments');
set_flashdata('Comment deleted successfully');
redirect('forum_comments/index');
}
}
This example assumes you have public and members_area templates created.
The $params Array
The optional $params array allows fine-grained control:
$params = [
'comment_id' => 42,
'action' => 'edit',
'user_role' => 'moderator'
];
$this->trongate_security->make_sure_allowed('moderate forum', $params);
Pass any data your scenario needs to make authorization decisions.
Dev Mode Auto-Login
The trongate_administrators module includes special behavior for development:
if ($token === false) {
if (ENV === 'dev') {
// Auto-generate token for first admin
// (convenience feature for development)
} else {
redirect('trongate_administrators/login');
}
}
In dev mode, the first administrator is automatically logged in. This speeds up development by eliminating repeated logins.
Production Safety: This auto-login only works when ENV === 'dev' in your config. Production deployments always require proper authentication.
Building Your Own Scenarios
To add custom scenarios:
- Open
Trongate_security.php
- Add a new case to the switch statement
- Either handle inline OR delegate to another module
- Return appropriate value (token, boolean, object, etc.)
Example: Premium Content
case 'premium content':
$user = $this->trongate_tokens->get_user_obj();
if ($user === false) {
redirect('members/login');
}
// Check if user has premium subscription
$sql = 'SELECT * FROM subscriptions WHERE trongate_user_id = ? AND status = ?';
$subscription = $this->db->query_bind($sql, [$user->trongate_user_id, 'active'], 'object');
if (empty($subscription)) {
redirect('subscriptions/upgrade');
}
return $user; // Return full user object with subscription info
Why Scenarios Win
- Centralized security logic - All authorization in one place
- Consistent patterns - Same method call everywhere
- Easy to audit - Review security in one file
- Flexible delegation - Complex logic lives in appropriate modules
- Testable - Mock scenarios for unit tests
Common Patterns
Pattern 1: Public Access
case 'public page':
return true;
Pattern 2: Simple Member Check
case 'members area':
$token = $this->trongate_tokens->attempt_get_valid_token(2);
if ($token === false) {
redirect('members/login');
}
return $token;
Pattern 3: Delegate to Module
case 'complex scenario':
$result = $this->custom_module->make_sure_allowed($scenario, $params);
return $result;
- Use descriptive scenario names -
'edit comment' not 'scenario_7'
- Delegate complex logic - Keep switch statement clean, push details to modules
- Return consistent types per scenario - Don't mix tokens and booleans for same scenario
- Document your scenarios - Add comments explaining what each scenario protects
- Test thoroughly - Especially ownership checks and edge cases