Symfony LDAP with custom User Entity and auto creation of DB user - symfony

I am trying to implement a simple LDAP authentication in my Symfony application.
A user should first be authenticated against LDAP, whereby a custom user entity should be returned from the database.
If a user is not in the database but could be authenticated successfully, I want to create the user.
Except for the automatic creation of the user in the database, it works so far.
providers:
users_db:
entity:
# the class of the entity that represents users
class: 'App\Entity\User'
# the property to query by - e.g. email, username, etc
property: 'username'
users_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: '%env(LDAP_BASE_DN)%'
search_dn: '%env(LDAP_SEARCH_DN)%'
search_password: '%env(LDAP_SEARCH_PASSWORD)%'
default_roles: ROLE_USER
uid_key: uid
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_db
http_basic_ldap:
service: Symfony\Component\Ldap\Ldap
dn_string: '%env(LDAP_DN_STRING)%'
With the above configuration, the auth runs against LDAP but the user comes from the DB. So if I have not created a corresponding user, the login attempt won't work.
I have tried the automatic creation of users via a UserChecker, a UserProvider and a LoginEventListener (listens to event onAuthenticationSuccess), unfortunately without success.
The onAuthenticationSuccess event is only called after successful authentication and can therefore not be used in the configuration described above, because the users_db provider does not (yet) contain the user, even if the LDAP Basic auth works.
Then I tried it with a UserChecker and chained provider [users_ldap, users_db]. This is also executed, but then I no longer get a user object but an LDAP user. So I tried to create my own UserProvider, unfortunately without success.
If anyone knows a good way to do this, I would appreciate an answer or a short comment. Thank you!

I've faced your problem before, I didn't able to save their informations automaticly in my local database, so I followes these steps:
signing in by using cas auth and Ldap as a user provider.
redirect them to form and retrieve them inside the fields like this:
<div>
{{ form_row(user_form.uid, { label: 'UID :*',
required: 'true',
attr: {
value : app.user.uid,
readonly: true
}
}) }}
</div>
After submiting the form thier roles will change from "ROLE_VISIT" to "ROLE_USER" and all LDAP information will be saved at my local database.
you can save some data privatly like this:
<div class="invisible" >
{{ form_row(user_form.genre,{
attr: { value : app.user.supannCivilite }
}) }}
</div>
I'm using this bundle for LDAP so please let me know if you need any help!
Also, you can take a look at my security configration as below, hope it will be useful:
providers:
chain_provider:
chain:
providers: [in_memory, database, ldap]
in_memory:
memory:
users:
__NO_USER__:
password:
roles: ROLE_ANON
database:
entity:
class: App\Entity\User
property: uid
ldap:
id: ldap_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
l3_firewall:
pattern: ^/
provider: chain_provider
security: true
guard:
authenticators:
- cas.security.authentication.authenticator
logout:
path: /logout
success_handler: authentication_handler
invalidate_session: false
access_denied_handler: App\EventListener\AccessDeniedListener
main:
pattern: ^/
security: true
lazy: true
provider: chain_provider
guard:
authenticators:
- cas.security.authentication.authenticator

Thanks to this answer I now got it working.
config/services.yaml
services:
App\Security\UserProvider:
arguments:
$em: '#Doctrine\ORM\EntityManagerInterface'
$ldap: '#Symfony\Component\Ldap\Ldap'
$baseDn: "%env(LDAP_BASE_DN)%"
$searchDn: "%env(LDAP_SEARCH_DN)%"
$searchPassword: "%env(LDAP_SEARCH_PASSWORD)%"
$defaultRoles: ["ROLE_USER"]
$uidKey: "uid"
$extraFields: []
App\EventListener\LoginListener:
arguments:
- "#doctrine.orm.entity_manager"
config/packages/security.yml
security:
enable_authenticator_manager: true
password_hashers:
App\Entity\User: 'auto'
providers:
users:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users
stateless: false
http_basic_ldap:
service: Symfony\Component\Ldap\Ldap
dn_string: 'uid={username},ou=accounts,dc=example,dc=com'
src/Security/UserProvider.php
<?php
namespace App\Security;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Ldap\Security\LdapUserProvider;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Ldap\Security\LdapUser;
class UserProvider extends LdapUserProvider
{
private $em;
public function __construct(EntityManagerInterface $em, Ldap $ldap, string $baseDn, string $searchDn = null, string $searchPassword = null, array $defaultRoles = [], string $uidKey = null, string $filter = null, string $passwordAttribute = null, array $extraFields = [])
{
parent::__construct($ldap, $baseDn, $searchDn, $searchPassword, $defaultRoles, $uidKey, $filter, $passwordAttribute, $extraFields);
$this->em = $em;
}
/**
* Refreshes the user after being reloaded from the session.
*
* #return UserInterface
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
$refreshUser = $this->em->getRepository(User::class)->findOneBy(['username' => $user->getUserIdentifier()]);
return $refreshUser;
}
/**
* Tells Symfony to use this provider for this User class.
*/
public function supportsClass(string $class): bool
{
return User::class === $class || is_subclass_of($class, User::class) || LdapUser::class === $class || is_subclass_of($class, LdapUser::class);
}
}
src/EventListener/LoginListener.php
<?php
namespace App\EventListener;
use App\Entity\User;
use Doctrine\ORM\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Ldap\Security\LdapUser;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class LoginListener implements EventSubscriberInterface
{
private $em;
private $tokenStorage;
function __construct(EntityManager $em, TokenStorageInterface $tokenStorage)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents(): array
{
return [LoginSuccessEvent::class => 'onLoginSuccess'];
}
public function onLoginSuccess(LoginSuccessEvent $loginSuccessEvent)
{
$ldapUser = $loginSuccessEvent->getAuthenticatedToken()->getUser();
if (!($ldapUser instanceof LdapUser)) {
return;
}
$localUser = $this->em->getRepository(User::class)->findOneBy(['username' => $ldapUser->getUserIdentifier()]);
if (!$localUser) {
// No local user found in database -> create new user
$localUser = new User();
$localUser->setUsername($ldapUser->getUserIdentifier());
}
// We don't store user passwords -> generate random token
$rmdBytes = random_bytes(32);
$localUser->setPassword($rmdBytes);
$this->em->persist($localUser);
$this->em->flush();
// Login user
$token = new UsernamePasswordToken($localUser, $rmdBytes, 'main', $localUser->getRoles());
$this->tokenStorage->setToken($token);
}
}
I also implemented EquatableInterface in the User entity as suggested in the referenced stackoverflow.

Related

Symfony 5 security.interactive_login event not called

I want to use the security.interactive_login event to update my User's last login field.
The event is successfully registered:
php bin/console debug:event-dispatcher security.interactive_login
Registered Listeners for "security.interactive_login" Event
===========================================================
------- ------------------------------------------------------------------------ ----------
Order Callable Priority
------- ------------------------------------------------------------------------ ----------
#1 App\EventSubscriber\UserLocaleSubscriber::onSecurityInteractiveLogin() 0
------- ------------------------------------------------------------------------ ----------
But it lands on Not called listeners in the Symfony profiler.
This is the event subscriber:
class UserLocaleSubscriber implements EventSubscriberInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
/** #var User $user */
$user = $event->getAuthenticationToken()->getUser();
$user->setLastLoginAt(new DateTime());
$this->em->persist($user);
$this->em->flush();
}
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
];
}
}
And there is my security.yaml file:
security:
enable_authenticator_manager: true
encoders:
App\Entity\User:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js|fonts)/
security: false
main:
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginAuthenticator
logout:
path: app_logout
target: app_login # where to redirect after logout
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: /
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/(?!login), roles: ROLE_ADMIN }
The LoginAuthenticator class is Symfony's default generated one.
Why the interactive login event is not called?
When using the new(ish) authentication manager, the INTERACTIVE_LOGIN event is replaced with the LoginSuccessEvent.
# my subscriber
public static function getSubscribedEvents()
{
return [
//SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
LoginSuccessEvent::class => 'onLoginSuccess'
];
}
public function onLoginSuccess(LoginSuccessEvent $event)
{
$user = $event->getUser();
$user->setCount($user->getCount() + 1);
$this->em->flush();
//dd($user);
}
I'm not sure if this is explicitly documented yet. Like many upgrade deprecations, the code is very confusing. I tried to trace through what was happening and quickly got lost (once again) in the Security forest.
Events are talked about here.
I discovered this behavior by creating a fresh 5.1 project, running make:auth and adding a listener for both events. But I forgot to add enable_authenticator_manager: true to the security config.
So the INTERACTIVE_LOGIN event was fired. After enabling the new manager, the LoginSuccessEvent was fired. Notice that the new event has some additional helper methods such as getUser. Makes the code a tiny bit cleaner.
Off-topic but I would caution against flushing the entity manager inside of a listener. It can be a bit unpredictable depending on what else is going on. Might consider just getting the database connection and executing a SQL update.

How to login via username or email using LexikJWT bundle for symfony5?

I am using LexikJWT and GraphQL for my symfony5 API but I don't know how to configure my app to login via username or email.
This is my security.yaml and lexik_jwt_authentication.yaml
// security.yaml
security:
encoders:
App\Entity\User:
algorithm: auto
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
// lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 604800
user_identity_field: username
You can implement a custom user provider.
Example:
class UserRepository extends ServiceEntityRepository implements UserLoaderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* Loads the user for the given username.
*
* This method must return null if the user is not found.
*
* #param $usernameOrEmail
* #return UserInterface|null
* #throws \Doctrine\ORM\NonUniqueResultException
*/
public function loadUserByUsername($usernameOrEmail)
{
return $this->createQueryBuilder('u')
->where('u.username = :query OR u.email = :query')
->setParameter('query', $usernameOrEmail)
->getQuery()
->getOneOrNullResult();
}
}

Reload user role after change without re-logging

How to refresh logged in user role e.g. when it has been changed by admin user? I've found the always_authenticate_before_granting security option (it's not included in Symfony 4 documentation) and set it to true.
security.yaml:
security:
always_authenticate_before_granting: true
encoders:
App\Entity\Main\User:
algorithm: bcrypt
providers:
app_user_provider:
entity:
class: App\Entity\Main\User
property: email
role_hierarchy:
ROLE_ADMIN: ROLE_USER
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
guard:
authenticators:
- App\Security\LoginFormAuthenticator
form_login:
login_path: login
check_path: login
logout:
path: logout
target: homepage
remember_me:
secret: '%kernel.secret%'
path: /
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/.*, roles: ROLE_USER }
but it doesn't take effect.
UPDATE
I've created onRequest subscriber:
class RequestSubscriber implements EventSubscriberInterface
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => 'onRequest'
];
}
public function onRequest(GetResponseEvent $event): void
{
if (!$event->isMasterRequest()) {
return;
}
if(!$token = $this->tokenStorage->getToken()) return;
$sessionUser = $token->getUser();
if ($sessionUser instanceof User) {
$this->tokenStorage->setToken(new PostAuthenticationGuardToken($sessionUser, 'main', $sessionUser->getRoles()));
}
}
}
and now I can refresh the updated roles on every request, but comparing sessionUser to databaseUser is pointless, because the sessionUser always contains newly updated roles, though in Symfony Profiler > Security Token are listed the old ones (in case when I don't set the new token, of course).
Tl;dr I'm afraid you will have to introduce a mechanism of your own in order to make this work.
The session token is stored inside the user's session. This will have quite an impact on your application's performance, because each time a call to the database will be required in order to check if the role had changed.
So you will need a request listener which will compare database role with current user role, and if it is not same, replace the token in the session, this time with new role list, eg. (pseudo code):
sessionUser = tokenStorage.token.user
databaseUser = userRepository.find(sessionUser.id)
if sessionUser.roles !== databaseUser.roles
tokenStorage.setToken(new PostAuthenticationGuardToken(…))
or use a cache as a flag carrier to notify the user about the change. This method is going to be much quicker for sure.
sessionUser = tokenStorage.token.user
// of course the flag has to be set when the user's role gets changed
cacheToken = 'users.role_reload.' . sessionUser.id
if cache.has(cacheToken)
databaseUser = userRepository.find(sessionUser.id)
tokenStorage.setToken(new PostAuthenticationGuardToken(…))
cache.remove(cacheToken)
Either way the user has to ask the application has there been role change, on each request.

FOSUserBundle - PHPUnit - Mock a user - No User Provider

I am using Symfony 3.2 with the FOSUserBundle and I am trying to write functional tests for function that require authentication in specific roles.
I used the approach posted by #tftd here, but when I run a phpunit test, I get a 500 error: There is no user provider for user "Symfony\Component\Security\Core\User\User".
My webTestCase class looks like this:
abstract class CustomWebTestCase extends WebTestCase
{
/**
* #param array|null $roles
* #return \Symfony\Bundle\FrameworkBundle\Client
*
* https://stackoverflow.com/questions/35565196/fosuserbundle-phpunit-mock-a-user
*/
protected static function createAuthenticatedClient(array $roles = null) {
// Assign default user roles if no roles have been passed.
if($roles == null) {
$role = new Role('ROLE_SUPER_ADMIN');
$roles = array($role);
} else {
$tmpRoles = array();
foreach($roles as $role)
{
$role = new Role($role);
$tmpRoles[] = $role;
}
$roles = $tmpRoles;
}
$user = new User('test_super_admin', 'passwd', $roles);
return self::createAuthentication(static::createClient(), $user);
}
private static function createAuthentication(Client $client, User $user) {
// Read below regarding config_test.yml!
$session = $client->getContainer()->get('session');
// Authenticate
$firewall = 'main'; // This MUST MATCH the name in your security.firewalls.->user_area<-
$token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
$session->set('_security_'.$firewall, serialize($token));
$session->save();
// Save authentication
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
return $client;
}
And my test routine looks like this:
class TryoutAdminControllerTest extends CustomWebTestCase
{
public function testTryoutListAction()
{
$authenticatedClient = self::createAuthenticatedClient(array('ROLE_USER'));
$crawler = $authenticatedClient->request('GET', '/admin/tryout');
$this->assertEquals(302, $authenticatedClient->getResponse()->getStatusCode(), 'No access allowed!');
$authorizedClient = self::createAuthenticatedClient(array('ROLE_ADMIN'));
$crawler = $authorizedClient->request('GET', '/admin/tryout');
$this->assertEquals(200, $authorizedClient->getResponse()->getStatusCode(), 'Access granted!');
}
}
security.yml:
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
logout: true
anonymous: true
switch_user: true
remember_me:
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
domain: ~
user_provider: fos_userbundle
And finally config_test.yml
imports:
- { resource: config_dev.yml }
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
web_profiler:
toolbar: false
intercept_redirects: false
swiftmailer:
disable_delivery: true
If someone could let me know what I'm missing, I'd appreciate it!
You probably need to instantiate your own User class that extends from FOS\UserBundle\Model\User instead of Symfony\Component\Security\Core\User\User in your WebTestCase since you are using the FosUserBundle user provider.
Consider using a test database and data fixtures as you will probably need it for your functional tests. The LiipFunctionalTestBundle may help.
Keep in mind that nothing is being mocked in this test, as suggested in the question title. Mocks must be used in unit tests, not in functional tests.

Combine a stateful application with stateless JWT

We have a FOSUserBundle login system authenticating via LDAP and the fr3d LDAP bundle. It behaves like a normal multiple page application using sessions. We also have several RESTful endpoints using the FOSRestbundle and normal sessions for authentication. However, we need to share a few end points with an external application.
We managed to implement JWT using the Lexik bundle. It returns a token just fine. However, I don't know the best way to let a user using our login form to get this token so their request can pass it along in the header or session. My question is how to allow a user to login to our application in a stateful manner, but also receive the JWT and pass it to the server on ajax requests. This way I can allow external clients to connect directly to the API. Below is my symfony2 security configuration, security.yml:
security:
#erase_credentials: false
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [my_user_provider, fr3d_ldapbundle]
in_memory:
memory:
users:
admin: { password: secret, roles: 'ROLE_ADMIN' }
my_user_provider:
id: app.custom_user_provider
fos_userbundle:
id: fos_user.user_provider.username
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api, role: IS_AUTHENTICATED_FULLY }
- { path: ^/api/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
fr3d_ldap: ~
provider: chain_provider
anonymous: true
stateless: false
form_login:
check_path: /api/login_check
username_parameter: username
password_parameter: password
require_previous_session: false
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
provider: chain_provider
stateless: false
lexik_jwt:
throw_exceptions: true
create_entry_point: true
main:
pattern: ^/
fr3d_ldap: ~
form_login:
# provider: fos_userbundle
provider: chain_provider
always_use_default_target_path: true
default_target_path: /
csrf_provider: security.csrf.token_manager
logout: true
anonymous: true
switch_user: { role: ROLE_LIMS-BIOINFO}
EDIT:
Based on Kévin's answer I decided to implement a custom Twig extension to get the token for the logged in user on each page load:
AppBundle/Extension/JsonWebToken.php:
<?php
namespace AppBundle\Extension;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class JsonWebToken extends \Twig_Extension
{
/**
* #var ContainerInterface
*/
private $container;
/**
* #var JWTManagerInterface
*/
private $jwt;
public function __construct(ContainerInterface $container, JWTManagerInterface $jwt)
{
$this->container = $container;
$this->jwt = $jwt;
}
public function getName()
{
return 'json_web_token';
}
public function getFunctions()
{
return [
'json_web_token' => new \Twig_Function_Method($this, 'getToken')
];
}
public function getToken()
{
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$token = $this->jwt->create($user);
return $token;
}
}
app/config/services.yml:
app.twig_jwt:
class: AppBundle\Extension\JsonWebToken
arguments: ["#service_container", "#lexik_jwt_authentication.jwt_manager"]
tags:
- { name: twig.extension }
app/Resources/views/layout.html.twig
<script>window.jsonWebToken = '{{ json_web_token() }}';</script>
app/Resources/modules/layout/app.js:
var jsonWebToken = window.jsonWebToken;
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization","Bearer " + jsonWebToken);
}
});
So far this seems to be working well. It let's my external API users and internal application users share the same authentication methods.
As the JWT token must be stored client-side (and not in a cookie to prevent CSRF attacks), you can use the create method of the lexik_jwt_authentication.jwt_manager service provided by LexikJWTAuthenticationBundle to generate a token after the login, then inject this token in a <script> tag in the generated HTML.
Hey i recently came across this same situation.
To generate the JWT I created a redirect listener
class RedirectListener implements EventSubscriberInterface
{
//the private variables go up here
public function __construct(\Twig_Environment $twig, TokenStorageInterface $sam, EntityManagerInterface $em, JWTTokenManagerInterface $JWTTokenManager)
{
$this->twig = $twig;
$this->sam = $sam;
$this->em = $em;
$this->JWTTokenManager = $JWTTokenManager;
}
public function kernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$route = $request->get('_route');
$routeParams = $request->get('_route_params');
$pathInfo = $request->getPathInfo();
$matchApp = preg_match('/\/app/', $pathInfo);
if ($event->isMasterRequest()) {
if ($matchApp) {
$token = $this->sam->getToken();
if ($token) {
/** #var User $user */
$user = $token->getUser();
if($user instanceof User){
$token = $this->JWTTokenManager->create($user);
$this->twig->addGlobal('jwt', $token);
}
}
}
}
return $event;
}
}
This helped me get the JWT to my twig template (I used my base template to make sure it's present on every page)
{% if jwt is defined %}
<span class="hidden" id="jwt" data-jwt="{{ jwt ? jwt : 'null' }}"></span>
{% endif %}
Now using autobahn JS I can subscribe using the JWT :
let jwt = $('#jwt').data('jwt');
let connection = new Connection({url:"ws://127.0.0.1:8080/ws", realm:"realm1"});
connection.onopen = (session) => {
function onevent(args) {
console.log("Event:", args[0])
}
session.subscribe(jwt, onevent);
}
connection.open();
Next the server can now receive messages with the JWT from the JS
public function __construct(ContainerInterface $container)
{
$router = new Router();
$realm = "realm1";
$router->addInternalClient(new Pusher($realm, $router->getLoop()));
$router->addTransportProvider(new RatchetTransportProvider("0.0.0.0", 8080));
try{
$router->start();
} catch (\Exception $exception){
var_dump($exception->getMessage());
}
}
I now need to register a module for the router that will act as a listener and send messages back to the registered topic (JWT).
I'm not 100% there yet so any advice would be appreciated and I'll keep this updated as I go along.

Resources