2025-05-25 01:47:36 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] helpers_jwtHelper_error_683276b825a4e4.61420688; Context: {
    "timestamp": "2025-05-25 01:47:36",
    "exceptionType": "UnexpectedValueException",
    "exceptionMessage": "Wrong number of segments",
    "exceptionCode": 0,
    "exceptionFile": "vendor\/firebase\/php-jwt\/src\/JWT.php",
    "exceptionLine": 109,
    "userId": "guest"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:47:36 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] cookie_jwt_auth_authenticate_error_683276b825c827.80290167; Context: {
    "timestamp": "2025-05-25 01:47:36",
    "exceptionType": "yii\\web\\UnauthorizedHttpException",
    "exceptionMessage": "Your token is invalid or expired.",
    "exceptionCode": 0,
    "exceptionFile": "models\/User.php",
    "exceptionLine": 590,
    "userId": "guest",
    "yiiSpecificInfo": "Yii Base Exception"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:47:40 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] helpers_jwtHelper_error_683276bc907314.75541407; Context: {
    "timestamp": "2025-05-25 01:47:40",
    "exceptionType": "UnexpectedValueException",
    "exceptionMessage": "Wrong number of segments",
    "exceptionCode": 0,
    "exceptionFile": "vendor\/firebase\/php-jwt\/src\/JWT.php",
    "exceptionLine": 109,
    "userId": "guest"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:47:40 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] cookie_jwt_auth_authenticate_error_683276bc908242.01080578; Context: {
    "timestamp": "2025-05-25 01:47:40",
    "exceptionType": "yii\\web\\UnauthorizedHttpException",
    "exceptionMessage": "Your token is invalid or expired.",
    "exceptionCode": 0,
    "exceptionFile": "models\/User.php",
    "exceptionLine": 590,
    "userId": "guest",
    "yiiSpecificInfo": "Yii Base Exception"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:47:40 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] helpers_jwtHelper_error_683276bc987fb7.00661646; Context: {
    "timestamp": "2025-05-25 01:47:40",
    "exceptionType": "UnexpectedValueException",
    "exceptionMessage": "Wrong number of segments",
    "exceptionCode": 0,
    "exceptionFile": "vendor\/firebase\/php-jwt\/src\/JWT.php",
    "exceptionLine": 109,
    "userId": "guest"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:47:53 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] helpers_jwtHelper_error_683276c95ddb71.85079338; Context: {
    "timestamp": "2025-05-25 01:47:53",
    "exceptionType": "UnexpectedValueException",
    "exceptionMessage": "Wrong number of segments",
    "exceptionCode": 0,
    "exceptionFile": "vendor\/firebase\/php-jwt\/src\/JWT.php",
    "exceptionLine": 109,
    "userId": "guest"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:47:53 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] cookie_jwt_auth_authenticate_error_683276c95de2c5.98062114; Context: {
    "timestamp": "2025-05-25 01:47:53",
    "exceptionType": "yii\\web\\UnauthorizedHttpException",
    "exceptionMessage": "Your token is invalid or expired.",
    "exceptionCode": 0,
    "exceptionFile": "models\/User.php",
    "exceptionLine": 590,
    "userId": "guest",
    "yiiSpecificInfo": "Yii Base Exception"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:47:53 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] helpers_jwtHelper_error_683276c95e2993.58319719; Context: {
    "timestamp": "2025-05-25 01:47:53",
    "exceptionType": "UnexpectedValueException",
    "exceptionMessage": "Wrong number of segments",
    "exceptionCode": 0,
    "exceptionFile": "vendor\/firebase\/php-jwt\/src\/JWT.php",
    "exceptionLine": 109,
    "userId": "guest"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:48:12 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] helpers_jwtHelper_error_683276dc57b6b7.94059595; Context: {
    "timestamp": "2025-05-25 01:48:12",
    "exceptionType": "UnexpectedValueException",
    "exceptionMessage": "Wrong number of segments",
    "exceptionCode": 0,
    "exceptionFile": "vendor\/firebase\/php-jwt\/src\/JWT.php",
    "exceptionLine": 109,
    "userId": "guest"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//app//helpers/LogHelper.php:261
2025-05-25 01:48:12 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] cookie_jwt_auth_authenticate_error_683276dc57bc89.21778034; Context: {
    "timestamp": "2025-05-25 01:48:12",
    "exceptionType": "yii\\web\\UnauthorizedHttpException",
    "exceptionMessage": "Your token is invalid or expired.",
    "exceptionCode": 0,
    "exceptionFile": "models\/User.php",
    "exceptionLine": 590,
    "userId": "guest",
    "yiiSpecificInfo": "Yii Base Exception"
}
    in /app/components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
2025-05-25 01:48:12 [::1][-][llr1he38jjblpomrfc18uiqoj1][error][application] helpers_jwtHelper_error_683276dc57f429.20196199; Context: {
    "timestamp": "2025-05-25 01:48:12",
    "exceptionType": "UnexpectedValueException",
    "exceptionMessage": "Wrong number of segments",
    "exceptionCode": 0,
    "exceptionFile": "vendor\/firebase\/php-jwt\/src\/JWT.php",
    "exceptionLine": 109,
    "userId": "guest"
}
    in /app//components/AppLogger.php:236
    in /app//components/AppLogger.php:141
    in /app//helpers/LogHelper.php:261
    
    
class ApiBaseController extends Controller
{

	public $enableCsrfValidation = false;

	public function actions()
	{
		return [
			'options' => [
				'class' => OptionsAction::class,
			],
		];
	}
	
	public function behaviors(): array
	{
		$behaviors = parent::behaviors();
		
		$behaviors['corsFilter'] =
		[
			'class' => Cors::class,
			'cors' => [
				'Origin' => $this->getAllowedOrigins(),
				'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
				
				// Frontend header sends to backend:
				'Access-Control-Allow-Headers' => ['Content-Type', 'Authorization', 'X-Requested-With', 'X-CSRF-Token'],
				
				// Backend header sends to frontend:
				'Access-Control-Expose-Headers' => ['X-CSRF-Token'],
				
				'Access-Control-Allow-Credentials' => true,
				'Access-Control-Max-Age' => 3600,
			],
		];
		
		$behaviors['contentNegotiator'] =
		[
			'class' => ContentNegotiator::class,
			'formats' => [
				'application/json' => Response::FORMAT_JSON,
			],
		];
		
		$behaviors['authenticator'] =
		[
			'class' => CompositeAuth::class,
			//'class' => \app\components\DebugCompositeAuth::class,
			'except' => ['options'],
			'authMethods' => [
				['class' => \app\components\CookieJwtAuth::class],// Cookie based for browsers - Check 'access_token' cookie
				['class' => \yii\filters\auth\HttpBasicAuth::class],// Header based for native app/api clients
				//['class' => \yii\filters\auth\HttpBasicAuth::class],// Try adding a simple explicit authentication filter temporarily:
			],
		];


final class ApiSignupController extends ApiBaseController
{
	public function actionStageOneVerify(): array
	{
		$request = \Yii::$app->request;
		if (!$request->isPost)
		{
			throw new BadRequestHttpException('Only POST requests are allowed.');
		}
		
		$user = \Yii::$app->user->identity;
		
		if (!$user)
		{
			throw new UnauthorizedHttpException('User not authenticated.');
		}


final class ApiSecurityController extends ApiBaseController
{
	public function actionRefreshJwtToken(): array
	{
		$request = \Yii::$app->request;
		
		if (!$request->isPost)
		{
			throw new BadRequestHttpException('Only POST requests are allowed.');
		}
		
		$response = \Yii::$app->response;
		
		$cookieName = JwtCookieHelper::getRefreshCookie();
		$refreshToken = $request->cookies->getValue($cookieName) 
			?? $request->post($cookieName) 
			?? $request->get($cookieName);// Uncomment to support GET params (e.g., native apps)
			
		if (!$refreshToken)
		{
			$response->statusCode = 401;
			
			return [
				'success' => false,
				'message' => 'No refresh token found'
			];
		}
		
		$userTokenRecord = UserRefreshToken::findValidByToken($refreshToken);
		if (!$userTokenRecord)
		{
			$response->statusCode = 401;
			
			return [
				'success' => false,
				'message' => 'Invalid or expired refresh token'
			];
		}
		
		$accessToken = JwtHelper::generateToken($userTokenRecord->user_key);
		$accessCookie = JwtCookieHelper::createAccessTokenCookie($userTokenRecord->user_key);
		$response->cookies->add($accessCookie);
		
		$response->statusCode = 200;
		
		return [
			'success' => true
		];
	}


class JwtHelper
{
	private const ALGORITHM = 'HS256';
	
	private static function getKey(): string
	{
		$key = $_ENV['JWT_SECRET_KEY'] ?? '';
		if ($key === false || empty($key))
		{
			throw new UnknownPropertyException('JWT_SECRET_KEY environment variable not set');
		}
		
		return $key;
	}
	
	public static function generateToken(string $userKey, int $expireSeconds = 3600): string
	{
		$issuedAt = time();
		$payload = [
			'iss' => $_ENV['JWT_ISS'] ?? '',// Issuer
			'aud' => $_ENV['JWT_AUD'] ?? '',// Audience
			'iat' => $issuedAt,
			'nbf' => $issuedAt,
			'exp' => $issuedAt + $expireSeconds,
			'sub' => $userKey,
		];
		
		return JWT::encode($payload, self::getKey(), self::ALGORITHM);
	}
	
	public static function validateToken(string $token): stdClass|false
	{
		try
		{
			/** @var stdClass $decoded */
			$decoded = JWT::decode($token, new Key(self::getKey(), self::ALGORITHM));
			
			return $decoded;
		}
		catch (\Throwable $e)
		{
			ExceptionLogger::log($e, 'helpers_jwtHelper_error_');
			
			return false;
		}
	}
}


class JwtCookieHelper
{
	private const ACCESS_COOKIE_NAME = '_access_token';
	private const REFRESH_COOKIE_NAME = '_refresh_token';
	
	public static function getAccessCookie(): string
	{
		return self::ACCESS_COOKIE_NAME;
	}
	
	public static function getRefreshCookie(): string
	{
		return self::REFRESH_COOKIE_NAME;
	}
	
	public static function createAccessTokenCookie(string $jwtToken, int $expireSeconds = 3600): Cookie
	{
		// Jwt token is set using the JwtHelper
		//$jwtToken = JwtHelper::generateToken($userKey, $expireSeconds);
		
		return new Cookie([
			'name' => self::getAccessCookie(),
			'value' => $jwtToken,
			'httpOnly' => YII_ENV_DEV ? false : true,// true cookie cannot be accessed by JavaScript in production.
			'secure' => YII_ENV_DEV ? false : true,// true if you use HTTPS in production
			'sameSite' => YII_ENV_DEV ? Cookie::SAME_SITE_NONE : Cookie::SAME_SITE_LAX,
			'path' => '/',// Ensure cookie always overrides previous cookies on all paths
			'expire' => time() + $expireSeconds,
		]);
	}
	
	public static function removeAccessTokenCookie(): Cookie
	{
		return new Cookie([
			'name' => self::getAccessCookie(),
			'value' => '',
			'httpOnly' => YII_ENV_DEV ? false : true,
			'secure' => YII_ENV_DEV ? false : true,
			'sameSite' => YII_ENV_DEV ? Cookie::SAME_SITE_NONE : Cookie::SAME_SITE_LAX,
			'path' => '/',
			'expire' => time() - 3600,
		]);
	}
	
	public static function createRefreshTokenCookie(string $refreshToken, int $expireSeconds = 86400): Cookie
	{
		return new Cookie([
			'name' => self::getRefreshCookie(),
			'value' => $refreshToken,
			'httpOnly' => YII_ENV_DEV ? false : true,
			'secure' => YII_ENV_DEV ? false : true,
			'sameSite' => YII_ENV_DEV ? Cookie::SAME_SITE_NONE : Cookie::SAME_SITE_LAX,
			'path' => '/',
			'expire' => time() + $expireSeconds,
		]);
	}
	
	public static function removeRefreshTokenCookie(): Cookie
	{
		return new Cookie([
			'name' => self::getRefreshCookie(),
			'value' => '',
			'httpOnly' => YII_ENV_DEV ? false : true,
			'secure' => YII_ENV_DEV ? false : true,
			'sameSite' => YII_ENV_DEV ? Cookie::SAME_SITE_NONE : Cookie::SAME_SITE_LAX,
			'path' => '/',
			'expire' => time() - 3600,
		]);
	}
}


class User extends ActiveRecord implements IdentityInterface
{
	public static function findIdentityByAccessToken($token, $type = null): IdentityInterface|null
	{
		\Yii::debug("User::findIdentityByAccessToken called with token: $token", __METHOD__);
		\Yii::getLogger()->flush(true);
		
		$decoded = JwtHelper::validateToken($token);
		if ($decoded === false)
		{
			\Yii::debug('JWT validation failed for token: ' . $token, __METHOD__);
			
			throw new UnauthorizedHttpException('Your token is invalid or expired.');
		}
		
		$userKey = $decoded->sub ?? null;
		
		\Yii::debug("Token subject (sub): " . ($userKey ?? 'null'), __METHOD__);
		
		if (empty($userKey) || !is_string($userKey))
		{
			throw new UnauthorizedHttpException('Invalid token subject.');
		}
		
		// Find the user by userKey, not integer ID
		$user = self::findOne(['user_key' => $userKey]);
		if ($user === null)
		{
			\Yii::debug("User not found for user_key: {$userKey}", __METHOD__);
		
			throw new UnauthorizedHttpException('User not found.');
		}
		
		\Yii::debug("User found: {$user->id}", __METHOD__);
		
		return $user;
	}
	
	public function loginByAccessToken($token, $type = null)//: IdentityInterface|null
	{
		return self::findIdentityByAccessToken($token, $type);
	}

what am i doing wrong?