I’m new in Symfony and php. I want to authenticate users based fusion auth.
This is the action list:
Users requests for /login endpoint with their username and password.
The API sends request to /api/user to authenticate user login operation
Then the fusion provides an api auth token
Users can send to /api endpoint with this token
If this token is valid, the API is going to response for resource
Configuration file:
security:
providers:
app_user_provider:
entity:
class: AppEntityUserUser
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
custom_authenticators:
- AppSecurityTokenAuthenticator
access_control:
- { path: ^//login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/api, roles: ROLE_USER }
- { path: ^/profile, roles: ROLE_USER }
- { path: ^/$, roles: PUBLIC_ACCESS }
- { path: ^/docs, roles: PUBLIC_ACCESS }
I’m getting request from this controller:
<?php
// src/Controller/SecurityController.php
namespace AppControllerSecurity;
use AppSecurityTokenAuthenticator;
use PHPUnitException;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentRoutingAttributeRoute;
use SymfonyContractsHttpClientExceptionClientExceptionInterface;
use SymfonyContractsHttpClientExceptionRedirectionExceptionInterface;
use SymfonyContractsHttpClientExceptionServerExceptionInterface;
use SymfonyContractsHttpClientExceptionTransportExceptionInterface;
use SymfonyContractsHttpClientHttpClientInterface;
class SecurityController extends AbstractController
{
private ?string $fusionAuthBaseUrl = null;
private ?string $authorization = null;
private ?string $applicationId = null;
private ?array $headers = null;
private ?HttpClientInterface $httpClient = null;
public function __construct(HttpClientInterface $httpClient)
{
$this->httpClient = $httpClient;
$this->fusionAuthBaseUrl = "https://example";
$this->authorization = "authtokenprovidedfromFusionAuth";
$this->applicationId = "applicationId";
$this->headers = [
'Authorization' =>$this->authorization,
'Content-Type' => 'application/json'
];
}
#[Route('/login', name: 'user-login', methods: ['POST'])]
public function login(Request $request, TokenAuthenticator $authenticator): JsonResponse
{
$content = json_decode($request->getContent(), true);
$email = $content['email'] ?? null;
$password = $content['password'] ?? null;
try {
// Call Fusion Auth Token endpoint to authenticate user
$response = $this->httpClient->request('POST', $this->fusionAuthBaseUrl . '/api/login', [
'headers' => $this->headers,
'json' => [
'loginId' => $email,
'password' => $password,
'applicationId' => $this->applicationId
],
]);
$data = $response->getContent();
return new JsonResponse($data);
} catch (ClientExceptionInterface $e) {
return new JsonResponse(
[
'error' => 'Client error',
'details' => $e->getMessage()
], 401);
} catch (ServerExceptionInterface $e) {
return new JsonResponse(
[
'error' => 'Server error',
'details'=> $e->getMessage()
], 500);
} catch (Exception $e) {
return new JsonResponse(
[
'error' => 'An unexpected error occurred',
'details' =>$e->getMessage()
], 500);
} catch (RedirectionExceptionInterface $e) {
return new JsonResponse(
[
'error'=>'Redirection exception',
'details'=>$e->getMessage()
], 500);
} catch (TransportExceptionInterface $e) {
return new JsonResponse(
[
'error'=>'Transport exception',
'details'=>$e->getMessage()
], 500);
}
}
}
User entity to store user id in database
<?php
namespace AppEntityUser;
use AppRepositoryUserUserRepository;
use DoctrineDBALTypesTypes;
use DoctrineORMMapping as ORM;
use SymfonyComponentSecurityCoreUserPasswordAuthenticatedUserInterface;
use SymfonyComponentSecurityCoreUserUserInterface;
#[ORMEntity(repositoryClass: UserRepository::class)]
#[ORMTable(name: 'users')]
class User implements UserInterface//, PasswordAuthenticatedUserInterface
{
#[ORMId]
#[ORMGeneratedValue]
#[ORMColumn(type: Types::GUID, unique: true, nullable: false)]
//Fusionauth id
private ?string $uuid = null;
#[ORMColumn(type: Types::TEXT)]
private ?string $token = null;
#[ORMColumn(type: Types::STRING, length: 255)]
private ?string $email = null;
#[ORMColumn(type: Types::STRING, length: 255)]
private ?string $roles = null;
public function getUuid(): ?string
{
return $this->uuid;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getRoles(): array
{
return [$this->roles];
}
public function setRoles(?string $roles): self
{
$this->roles = $roles;
return $this;
}
public function getSalt(): void
{
}
public function getUsername(): string
{
return (string) $this->email;
}
public function setToken (?string $token): static
{
$this->token = $token;
return $this;
}
public function getToken(): ?string
{
return $this->token;
}
public function eraseCredentials()
{
}
public function getUserIdentifier(): string
{
return (string) $this->token;
}
public function getPassword(): ?string
{
return null;
}
}
This is the user provider:
<?php
namespace AppSecurity;
use AppEntityUserUser;
use AppRepositoryAppUserRepository;
use PharIoVersionException;
use SymfonyComponentSecurityCoreExceptionUnsupportedUserException;
use SymfonyComponentSecurityCoreUserPasswordAuthenticatedUserInterface;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentSecurityCoreUserUserProviderInterface;
class UserProvider implements UserProviderInterface//, PasswordAuthenticatedUserInterface
{
private ?UserRepository $userRepository = null;
public function __construct(?UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function loadUserByUsername(?string $username): UserInterface
{
$user = $this->userRepository->findOneByUsername($username);
if (!$user) {
throw new Exception(sprintf('User with username "%s" not found.', $username));
}
return $user;
}
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
}
$refreshedUser = $this->userRepository->find($user->getUuid());
if (!$refreshedUser) {
throw new Exception('User not found.');
}
return $refreshedUser;
}
public function supportsClass($class): bool
{
return User::class === $class;
}
public function loadUserByIdentifier(string $identifier): UserInterface
{
$user = $this->userRepository->findOneByIdentifier($identifier);
if (!$user)
{
throw new Exception(sprintf('User with identifier "%s" not found.', $identifier));
}
return $user;
}
}
I’m trying to authenticate user with this authenticator:
<?php
// src/Security/TokenAuthenticator.php
namespace AppSecurity;
use AppEntityUserUser;
use DoctrineORMEntityManagerInterface;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingRouterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreExceptionAuthenticationException;
use SymfonyComponentSecurityCoreExceptionCustomUserMessageAuthenticationException;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentSecurityCoreUserUserProviderInterface;
use SymfonyComponentSecurityHttpAuthenticatorAbstractAuthenticator;
use SymfonyComponentSecurityHttpAuthenticatorPassportBadgeUserBadge;
use SymfonyComponentSecurityHttpAuthenticatorPassportCredentialsCustomCredentials;
use SymfonyComponentSecurityHttpAuthenticatorPassportCredentialsPasswordCredentials;
use SymfonyComponentSecurityHttpAuthenticatorPassportPassport;
class TokenAuthenticator extends AbstractAuthenticator
{
private ?UserProvider $userProvider = null;
public function __construct(UserProvider $userProvider)
{
$this->userProvider = $userProvider;
}
public function supports(Request $request): ?bool
{
return $request->headers->has('Authorization') && str_starts_with($request->headers->get('Authorization'), 'Bearer ');
}
public function getCredentials(Request $request): array
{
$token = substr($request->headers->get('Authorization'), 7); // Remove 'Bearer ' prefix
return ['token' => $token];
}
public function getUser($credentials): ?UserInterface
{
$token = $credentials['token'];
$user = $this->userProvider->loadUserByIdentifier($token);
if (!$user)
{
throw new CustomUserMessageAuthenticationException('Invalid token');
}
return $user;
}
public function authenticate(Request $request): Passport
{
$credentials = $this->getCredentials($request);
$user = $this->getUser($credentials);
if (!$user) {
throw new CustomUserMessageAuthenticationException('Invalid token.');
}
return new Passport(
new UserBadge($user->getUserIdentifier())
new CustomCredentials(function (?array $credentials){
return true;
}, []
));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
// Doğrulama başarısız olduğunda yapılacak işlemler
return new JsonResponse(['error' => 'Authentication failed error',
'exception' => $exception->getMessage(),
'request-user' => $request->getUser(),
'request' => $request->toArray(),
'request-host' => $request->getHost(),
'trace' => $exception->getTrace()
], Response::HTTP_UNAUTHORIZED);
}
}
When I try to /login with my credentials, I’m getting a token from fusion. Now I need to get this token to access to the /api endpoint but when I try to send GET request with Authorization Bearer token, the API responses like that:
{
"@id": "/api/errors/400",
"@type": "hydra:Error",
"title": "An error occurred",
"detail": "Request body is empty.",
"status": 400,
"type": "/errors/400",
"trace": [
{
"file": "C:\Users\Onur\Desktop\yakalamac\vendor\symfony\http-kernel\Kernel.php",
"line": 197,
"function": "handle",
"class": "Symfony\Component\HttpKernel\HttpKernel",
"type": "->"
},
{
"file": "C:\Users\Onur\Desktop\yakalamac\vendor\symfony\runtime\Runner\Symfony\HttpKernelRunner.php",
"line": 35,
"function": "handle",
"class": "Symfony\Component\HttpKernel\Kernel",
"type": "->"
},
{
"file": "C:\Users\Onur\Desktop\yakalamac\vendor\autoload_runtime.php",
"line": 29,
"function": "run",
"class": "Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner",
"type": "->"
},
{
"file": "C:\Users\Onur\Desktop\yakalamac\public\index.php",
"line": 5,
"function": "require_once"
}
],
"hydra:title": "An error occurred",
"hydra:description": "Request body is empty."
}
But this is a normal GET request, so it’s no need to have a body. Also I don’t know how can I check this token does valid. Can you give me some idea about this please.