Files
Ecobuddy/Models/AuthService.php
2025-03-15 01:59:16 +00:00

199 lines
6.3 KiB
PHP

<?php
require_once('UserDataSet.php');
/**
* Authentication service for handling JWT-based authentication
*/
class AuthService {
private string $secretKey;
private int $tokenExpiry;
/**
* Initialises the authentication service
* Loads configuration from environment variables
* @throws Exception if OpenSSL extension is not loaded
*/
public function __construct() {
// Load environment variables from .env file
$envFile = __DIR__ . '/../.env';
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
// Skip comments
if (strpos($line, '#') === 0) continue;
// Parse environment variable
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
if (!empty($name)) {
putenv(sprintf('%s=%s', $name, $value));
}
}
}
// Set configuration from environment variables with defaults
$this->secretKey = getenv('JWT_SECRET_KEY') ?: 'your-256-bit-secret';
$this->tokenExpiry = (int)(getenv('JWT_TOKEN_EXPIRY') ?: 3600);
// Verify OpenSSL extension is available
if (!extension_loaded('openssl')) {
throw new Exception('OpenSSL extension is required for JWT');
}
}
/**
* Generates a JWT token for a user
* @param array $userData User information to include in token
* @return string The generated JWT token
*/
public function generateToken(array $userData): string {
$issuedAt = time();
$expire = $issuedAt + $this->tokenExpiry;
$payload = [
'iat' => $issuedAt,
'exp' => $expire,
'uid' => $userData['id'],
'username' => $userData['username'],
'accessLevel' => $userData['userType']
];
return $this->encodeJWT($payload);
}
/**
* Validates a JWT token
* @param string $token The JWT token to validate
* @return array|null The decoded payload if valid, null otherwise
*/
public function validateToken(string $token): ?array {
try {
$payload = $this->decodeJWT($token);
// Check if token is expired
if ($payload === null || !isset($payload['exp']) || $payload['exp'] < time()) {
return null;
}
return $payload;
} catch (Exception $e) {
return null;
}
}
/**
* Encodes data into a JWT token
* @param array $payload The data to encode
* @return string The encoded JWT token
*/
private function encodeJWT(array $payload): string {
// Create and encode header
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
$header = $this->base64UrlEncode($header);
// Create and encode payload
$payload = json_encode($payload);
$payload = $this->base64UrlEncode($payload);
// Create and encode signature
$signature = hash_hmac('sha256', "$header.$payload", $this->secretKey, true);
$signature = $this->base64UrlEncode($signature);
return "$header.$payload.$signature";
}
/**
* Decodes a JWT token
* @param string $token The JWT token to decode
* @return array|null The decoded payload if valid, null otherwise
*/
private function decodeJWT(string $token): ?array {
// Split token into components
$parts = explode('.', $token);
if (count($parts) !== 3) {
return null;
}
[$header, $payload, $signature] = $parts;
// Verify signature
$validSignature = $this->base64UrlEncode(
hash_hmac('sha256', "$header.$payload", $this->secretKey, true)
);
if ($signature !== $validSignature) {
return null;
}
// Decode and return payload
return json_decode($this->base64UrlDecode($payload), true);
}
/**
* Encodes data using base64url encoding
* @param string $data The data to encode
* @return string The encoded data
*/
private function base64UrlEncode(string $data): string {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
/**
* Decodes base64url encoded data
* @param string $data The data to decode
* @return string The decoded data
*/
private function base64UrlDecode(string $data): string {
return base64_decode(strtr($data, '-_', '+/') . str_repeat('=', 3 - (3 + strlen($data)) % 4));
}
/**
* Generates a refresh token for a user
* @param array $userData User information to include in token
* @return string The generated refresh token
*/
public function generateRefreshToken(array $userData): string {
$issuedAt = time();
$expire = $issuedAt + ($this->tokenExpiry * 24); // Refresh token lasts 24 times longer than access token
$payload = [
'iat' => $issuedAt,
'exp' => $expire,
'uid' => $userData['id'],
'username' => $userData['username'],
'type' => 'refresh'
];
return $this->encodeJWT($payload);
}
/**
* Refreshes an access token using a refresh token
* @param string $refreshToken The refresh token
* @return string|null The new access token if valid, null otherwise
*/
public function refreshToken(string $refreshToken): ?string {
try {
$payload = $this->decodeJWT($refreshToken);
// Check if token is expired or not a refresh token
if ($payload === null || !isset($payload['exp']) || $payload['exp'] < time() ||
!isset($payload['type']) || $payload['type'] !== 'refresh') {
return null;
}
// Generate a new access token
$userData = [
'id' => $payload['uid'],
'username' => $payload['username'],
'userType' => isset($payload['accessLevel']) ? $payload['accessLevel'] : 0
];
return $this->generateToken($userData);
} catch (Exception $e) {
return null;
}
}
}