Skip to content

Instantly share code, notes, and snippets.

@faizanakram99
Last active August 27, 2024 20:16
Show Gist options
  • Save faizanakram99/78fe736f0eb83298ceed2172002d71f3 to your computer and use it in GitHub Desktop.
Save faizanakram99/78fe736f0eb83298ceed2172002d71f3 to your computer and use it in GitHub Desktop.
Enums as an alternative to exceptions
<?php
declare(strict_types=1);
namespace Qbil\Infrastructure\Http\Security;
use Zzz\Infrastructure\Http\InvalidToken;
use Zzz\Infrastructure\Http\Token;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class AccessTokenHandler implements AccessTokenHandlerInterface
{
public function __construct(private TokenExtractor $tokenExtractor)
{
}
public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge
{
$token = $this->tokenExtractor->extract($accessToken);
return match ($token::class) {
Token::class => new UserBadge(
$token->username,
fn (string $userIdentifier) => new User($userIdentifier),
),
InvalidToken::class => throw new CustomUserMessageAuthenticationException($token->value),
};
}
}
<?php
declare(strict_types=1);
namespace Zzz\Infrastructure\Http;
enum InvalidToken: string
{
case TokenExpired = 'Token expired.';
case InvalidHost = 'Invalid host.';
case MalformedToken = 'Malformed token.';
case TokenNotValid = 'Token not valid.';
}
<?php
declare(strict_types=1);
namespace Zzz\Infrastructure\Http;
readonly class Token
{
public function __construct(
public string $username,
public int $validity,
public string $host,
) {}
}
<?php
declare(strict_types=1);
namespace Zzz\Infrastructure\Http\Security;
use Psr\Clock\ClockInterface;
use Zzz\Infrastructure\Http\InvalidToken;
use Zzz\Infrastructure\Http\Token;
class TokenExtractor
{
public function __construct(
private readonly ClockInterface $clock,
private readonly string $remoteServerPublicKey,
private readonly array $allowedHosts,
) {
}
public function extract(string $tokenString): Token|InvalidToken
{
$token = \explode(':', $tokenString);
if ((int) $token[1] <= $this->clock->now()->getTimestamp()) {
return InvalidToken::TokenExpired;
}
if (!\in_array($token[2], $this->allowedHosts, true)) {
return InvalidToken::InvalidHost;
}
$base64EncodedSignature = \array_pop($token);
if (false === $signature = \base64_decode($base64EncodedSignature, true)) {
return InvalidToken::MalformedToken;
}
\openssl_public_decrypt($signature, $hash, $this->remoteServerPublicKey);
if (!\hash_equals($hash, \hash('sha256', \implode(':', $token)))) {
return InvalidToken::TokenNotValid;
}
return new Token(
$token[0],
(int) $token[1],
$token[2],
);
}
}
<?php
declare(strict_types=1);
namespace Qbil\Infrastructure\Http\Security;
use Symfony\Component\Security\Core\User\UserInterface;
readonly class User implements UserInterface
{
public function __construct(public string $email) {}
public function getUserIdentifier(): string
{
return $this->email;
}
public function getRoles(): array
{
return ['ROLE_USER'];
}
public function eraseCredentials(): void
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment