I want to log the issed and encoded JWT token into the database. I am using my JWTlogger, which is initiated by the event JWTcreated. It works fine, I just do not know how to get the encoded jwt string. I know storing it in the DB is not a great idea, but this is a test task.
The method $this->tokenStorage->getToken()
returns UsernamePasswordToken(user="admin#admin.com", roles="ROLE_USER")
I want the whole encoded token.
<?php
namespace App\Security;
use App\Entity\Token;
use App\Entity\User;
use Doctrine\Persistence\ManagerRegistry;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\JWTAuthenticator;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class JWTLogger extends JWTAuthenticator
{
private $doctrine;
private $tokenStorage;
public function __construct(ManagerRegistry $doctrine, TokenStorageInterface $tokenStorage)
{
$this->doctrine = $doctrine;
$this->tokenStorage = $tokenStorage;
}
/**
* #param JWTCreatedEvent $event
*
* #return void
*/
public function onJWTCreated(JWTCreatedEvent $event)
{
$this->logJWTToken($event->getUser());
}
private function logJWTToken(User $user): void
{
$entityManager = $this->doctrine->getManager();
$dbtoken = new Token();
// insert encoded token here
$dbtoken->setToken($this->tokenStorage->getToken());
$dbtoken->setUserId($user);
$entityManager->persist($dbtoken);
$entityManager->flush();
}
}
One approach is to implement your own authenticator for JWT token creation.
Here you can find the link for
how to create custom authenticator
We implemented our own token issuer using lexik JWT bundle methods. First we got email and password from request and used symfony passport to validate the user, after validation we issued the token in onAuthenticationSuccess method by using JWTTokenManagerInterface method createFromPayload with custom information, you can decode your already issued token to check current payload so you can set the payload accordingly or with extra information but beware as it is decodable by base64. At the time when you issue the token you can save it in the database immediately.
Or you can grab the token in api authenticator from header either using TokenExtractor or $request->headers->get('Authorization') and save it in the database.
For further event list you can find all the events here:
JWT events by using you can get the token I think.
Related
How can I fetch the currently logged in User from anywhere within the Backend code? For example I have an EventSubscriber class and want to fetch it from there.
How can I do that w/o the help of i.e. AbstractController?
Symfony AbstractController is the core of most Controllers. Including EasyAdmin crud controller (XXXCrudController) extends AbstractController so you can access the same methods.
One of those is getUser() which return the current logged in user.
* Get a user from the Security Token Storage.
*
* #return UserInterface|null
*
* #throws \LogicException If SecurityBundle is not available
*
* #see TokenInterface::getUser()
*/
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return null;
}
// #deprecated since 5.4, $user will always be a UserInterface instance
if (!\is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return null;
}
return $user;
}
So when trying to get the logged used in a controller, just use this method.
If you want to get the same thing, but for example in a service, you can basically do the same as what the method actually does by using the service injection with TokenStorageInterface to access the TokenStorage service which can get the current user.
So in your event subscriber, add TokenStorageInterface in your constructor to use it to first get the token and then your user. You may have to add another check to see if there is an user logged in (by checking if there is a token for example)
//YourService.php
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
private $tokenStorage
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function yourMethod()
{
//get token then user
$user = $tokenStorage->getToken()->getUser();
}
I am writing Symfony Cookie based Authenticator. After getting response from configured UserProvider (remote service call) I would need to set cookie in the final Response. I don't know how can I access Response object to add new Cookie to it's headers at this stage.
The code for adding a Cookie is normally like this:
$cookie = new Cookie('foo', 'bar', strtotime('Wed, 28-Dec-2016 15:00:00 +0100'), '/', '.example.com', true, true, true),
$response->headers->setCookie(new Cookie('foo', 'bar'));
I need reference to $response
I do not want to create my own instance of Response and return it, since I would like to leave Response creation as it is, but I would only need this one cookie to be added to Response. What is best way to achieve this in Symfony 5?
This is simplified Authenticator code I am using:
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class SessionCookieAuthenticator extends AbstractGuardAuthenticator
{
public function supports(Request $request)
{
// check if php-sid cookie is provided
return !empty($request->cookies->get('php-sid'));
}
/**
* Credentials are global cookies
* #param Request $request
* #return mixed|string
*/
public function getCredentials(Request $request)
{
$cookie = implode('; ', array_map(
function($k, $v) {
return $k . '=' . $v;
},
array_keys($_COOKIE),
$_COOKIE
));
return $cookie;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return $userProvider->loadUserByCookie($credentials);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null; // #todo set Cookie here for example. Can I get Response here?
}
}
UserProvider should not set a Cookie, because the Cookie has nothing to do with providing a User. I would suggest to set the Cookie elsewhere (for instance, creating an event listener for security.authentication.success or directly inside your Authenticator.
Edit
onAuthenticationSuccess lets you return a Response (you, basically, need to create it). Upon that response, you can set the needed cookie.
I am trying to implement the LexikJwt package for my new API:
https://github.com/lexik/LexikJWTAuthenticationBundle
I just added my custom command to create a proper JWT token including my default roles for local testing. The next step would be to actually protect my routes. What i can't find however is exactly on how to do that. This is my current controller implementation using annotation routes
/**
* #Route(
* "/search",
* name="search",
* methods={"GET"},
* )
*/
class Search
{
/**
* #param AddressService $addressService
* #param Request $request
* #return JsonApiResponse
* #throws Exception
*/
public function __invoke(AddressService $addressService, Request $request)
{
$address = $addressService->createFromParams($request->query->all());
try {
$addressCollection = $addressService->search($address);
} catch (AddressNotFoundException $e) {
$addressCollection = [];
}
return new JsonApiResponse($addressCollection);
}
}
However the docs do not say anything about annotation routes and only on yml configs on security firewalls. The main thing i need is the token to be verified:
Is the token valid (matching the public key)
Is the token not expired
Does the route match the given roles
For example, i want the code above, which is an address service to match only if the token matches above and the token holds the role or scope: address:search.
Hope someone can help,
Pim
The issue was that the role should start with ROLE_. It is possible to override it though but this was the use case.
how do i get the password eneterd via a costum user provider. I found this question:
Symfony Security / Custom User Provider : How to get the login password within the custom User Provider?
Which says i would have to overrise the loadUserByName method, and add the password as a parameter, the issue is that i cannot find the file he overrides:
security_listeners.xml
Where is this file located?
I found the:
$user = $this->userProvider->loadUserByUsername($username);
call in the DOAAuthenticationProvider, and i see that the method takes:
protected function retrieveUser($username, UsernamePasswordToken $token)
as augments, i asume i need to pass the password there, and then pass it to loadUserByName, which calls my costum userprovider method.
Any help on how to achieve this is appreciated.
You can create an AuthenticationHandler on your bundle. and the on successful login fetch the password from the token.
in services.yml:
security.authentication.success_handler:
class: Wix\UserBundle\EventListener\AuthenticationHandler
arguments: ["#security.http_utils", {}]
tags:
- { name: 'monolog.logger', channel: 'security' }
And sample class:
class AuthenticationHandler extends DefaultAuthenticationSuccessHandler
{
/**
* This is called when an interactive authentication attempt succeeds. This
* is called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* #param Request $request
* #param TokenInterface $token
*
* #return Response never null
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$response = new RedirectResponse('/');
$response->setStatusCode(200);
return $response;
}
}
I'm busy with a Symfony2 application that needs some ACL permissions.
I'm a newbie with Symfony2, so not sure if i'm looking at this the right way.
I have multiple clients, each with multiple accounts.
I have a super admin (ROLE_SUPER_ADMIN) that have access to all clients and all accounts.
Then I have an admin role (ROLE_ADMIN), which will only be allowed access to a specific client and all accounts for that clients.
Then there is agents (ROLE_AGENT), which should only have permission to certain accounts for clients.
I saw on the symfony docs that to give a user access to a specific object, I can use the following code:
// creating the ACL
$aclProvider = $this->get('security.acl.provider');
$objectIdentity = ObjectIdentity::fromDomainObject($account);
$acl = $aclProvider->createAcl($objectIdentity);
// retrieving the security identity of the currently logged-in user
$securityContext = $this->get('security.context');
$user = $securityContext->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant owner access
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
So when creating a new account, I can give the current logged-in user access to the newly created account.
But how do I grant access to all the other users of the client access to the account?
I don't want to loop through all users and run the above code for every user.
So for example when viewing all clients, I need to know which clients the user has access to, or when viewing the accounts, I need to know which accounts the user has access to.
Also when adding a new user to a client, the user automatically need to have access to all the accounts for that client.
As a side note, I only need to know if the user has access to the account/client. If a user has access, then they are automatically allowed to view/edit/delete etc.
For this case I used a custom security service that verifies ManyToMany relations between entities. It`s not the ideal decision but keep in mind.
First we need to make listener that will be triggered at every controller action.
class SecurityListener
{
protected $appSecurity;
function __construct(AppSecurity $appSecurity)
{
$this->appSecurity = $appSecurity;
}
public function onKernelController(FilterControllerEvent $event)
{
$c = $event->getController();
/*
* $controller passed can be either a class or a Closure. This is not usual in Symfony2 but it may happen.
* If it is a class, it comes in array format
*/
if (!is_array($c)) {
return;
}
$hasAccess = $this->appSecurity->hasAccessToContoller($c[0], $c[1], $event->getRequest());
if(!$hasAccess) {
throw new AccessDeniedHttpException('Access denied.');
}
}
}
In service we have access to request, controller instance and called action. So we can make a decision have user access or not.
class AppSecurity
{
protected $em;
protected $security;
/** #var $user User */
protected $user;
public function __construct(EntityManager $em, SecurityContext $security)
{
$this->em = $em;
$this->security = $security;
if($security->getToken() !== null && !$security->getToken() instanceof AnonymousToken) {
$this->user = $security->getToken()->getUser();
}
}
/**
* #param $controller
* #param string $action
*/
public function hasAccessToContoller($controller, $action, Request $request)
{
$attrs = $request->attributes->all();
$client = $attrs['client'];
/* db query to check link between logged user and request client */
}
}
If you are using very nasty annotations like ParamConverter you can easily extract ready to use entites from request.