Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions src/Auth/Abstracts/AbstractTokenController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace MRussell\REST\Auth\Abstracts;

/**
* Class AbstractTokenController
*
* A simple token-based authentication controller for APIs that use
* pre-configured API tokens passed via Bearer Authorization header.
* No authenticate/logout flow required.
*
* Usage Example:
* ```php
* $auth = new TokenAuthController();
*
* // Use 'token' property (recommended)
* $auth->setCredentials(['token' => 'your-api-token-here']);
*
* // Or use 'api_token' property (backward compatibility)
* $auth->setCredentials(['api_token' => 'your-api-token-here']);
*
* // The token will be automatically added to requests as:
* // Authorization: Bearer your-api-token-here
*
* // Check if authenticated
* if ($auth->isAuthenticated()) {
* // Token is set and ready to use
* }
* ```
*
* @package MRussell\REST\Auth\Abstracts
*/
abstract class AbstractTokenController extends AbstractBasicController
{
public const DEFAULT_AUTH_TYPE = 'Bearer';

protected string $authType = self::DEFAULT_AUTH_TYPE;

/**
* Token-based auth doesn't require authenticate/logout actions
*/
protected static array $_DEFAULT_AUTH_ACTIONS = [];

/**
* @inheritdoc
*/
public function setCredentials(array $credentials): static
{
parent::setCredentials($credentials);

// If a token is provided in credentials, set it directly
// Support both 'token' and 'api_token' for backward compatibility
if (isset($credentials['token'])) {
$this->setToken($credentials['token']);
} elseif (isset($credentials['api_token'])) {
$this->setToken($credentials['api_token']);
}

return $this;
}

/**
* Token-based auth is authenticated if a token is present
* @inheritdoc
*/
public function isAuthenticated(): bool
{
return !empty($this->token);
}

/**
* For token-based auth, authentication is always successful if token is set
* No API call needed
* @inheritdoc
*/
public function authenticate(): bool
{
return $this->isAuthenticated();
}

/**
* For token-based auth, logout just clears the token
* No API call needed
* @inheritdoc
*/
public function logout(): bool
{
$this->clearToken();
$this->removeCachedToken();
return true;
}

/**
* Get the Value to be set on the Auth Header
* For token auth, just use the token directly
*/
protected function getAuthHeaderValue(): string
{
return $this->authType . " " . $this->getToken();
}
}
7 changes: 7 additions & 0 deletions src/Auth/TokenAuthController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace MRussell\REST\Auth;

use MRussell\REST\Auth\Abstracts\AbstractTokenController;

class TokenAuthController extends AbstractTokenController {}
151 changes: 151 additions & 0 deletions tests/Auth/AbstractTokenControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace MRussell\REST\Tests\Auth;

use MRussell\REST\Auth\TokenAuthController;
use PHPUnit\Framework\TestCase;

/**
* Class AbstractTokenControllerTest
* @package MRussell\REST\Tests\Auth
* @coversDefaultClass \MRussell\REST\Auth\Abstracts\AbstractTokenController
* @group AbstractTokenControllerTest
*/
class AbstractTokenControllerTest extends TestCase
{
public static function setUpBeforeClass(): void
{
//Add Setup for static properties here
}

public static function tearDownAfterClass(): void
{
//Add Tear Down for static properties here
}

protected function setUp(): void
{
parent::setUp();
}

protected function tearDown(): void
{
parent::tearDown();
}

/**
* @covers ::setCredentials
* @covers ::isAuthenticated
*/
public function testSetCredentials(): void
{
$Auth = new TokenAuthController();
$this->assertEquals(false, $Auth->isAuthenticated());

$Auth->setCredentials(['token' => 'my-api-token-12345']);
$this->assertEquals(true, $Auth->isAuthenticated());
$this->assertEquals('my-api-token-12345', $Auth->getToken());
}

/**
* @covers ::setCredentials
* @covers ::isAuthenticated
*/
public function testSetCredentialsWithApiToken(): void
{
$Auth = new TokenAuthController();
$this->assertEquals(false, $Auth->isAuthenticated());

// Test backward compatibility with 'api_token' property
$Auth->setCredentials(['api_token' => 'my-api-token-67890']);
$this->assertEquals(true, $Auth->isAuthenticated());
$this->assertEquals('my-api-token-67890', $Auth->getToken());
}

/**
* @covers ::setCredentials
* @covers ::isAuthenticated
*/
public function testSetCredentialsTokenTakesPrecedence(): void
{
$Auth = new TokenAuthController();

// If both 'token' and 'api_token' are provided, 'token' should take precedence
$Auth->setCredentials([
'token' => 'token-value',
'api_token' => 'api-token-value'
]);
$this->assertEquals(true, $Auth->isAuthenticated());
$this->assertEquals('token-value', $Auth->getToken());
}

/**
* @covers ::authenticate
*/
public function testAuthenticate(): void
{
$Auth = new TokenAuthController();

// Authentication should fail without token
$this->assertEquals(false, $Auth->authenticate());

// Authentication should succeed with token
$Auth->setCredentials(['token' => 'my-api-token-12345']);
$this->assertEquals(true, $Auth->authenticate());
}

/**
* @covers ::logout
* @covers ::isAuthenticated
*/
public function testLogout(): void
{
$Auth = new TokenAuthController();
$Auth->setCredentials(['token' => 'my-api-token-12345']);

$this->assertEquals(true, $Auth->isAuthenticated());
$this->assertEquals(true, $Auth->logout());
$this->assertEquals(false, $Auth->isAuthenticated());
}

/**
* @covers ::__construct
*/
public function testNoAuthActions(): void
{
$Auth = new TokenAuthController();

// Token auth should not have authenticate/logout actions
$actions = $Auth->getActions();
$this->assertEmpty($actions);
}

/**
* Test setting token directly
*/
public function testSetToken(): void
{
$Auth = new TokenAuthController();
$Auth->setToken('direct-token-67890');

$this->assertEquals(true, $Auth->isAuthenticated());
$this->assertEquals('direct-token-67890', $Auth->getToken());
}

/**
* @covers ::getAuthHeaderValue
*/
public function testAuthHeaderValue(): void
{
$Auth = new TokenAuthController();
$Auth->setCredentials(['token' => 'my-api-token-12345']);

// Use reflection to test protected method
$class = new \ReflectionClass($Auth);
$method = $class->getMethod('getAuthHeaderValue');
$method->setAccessible(true);

$headerValue = $method->invoke($Auth);
$this->assertEquals('Bearer my-api-token-12345', $headerValue);
}
}