authentication in symfony api with ldaptools+jwt+fosuser - symfony

In my api I'm trying to authenticate a user using LdapToolsBundle, FOSUserBundle and LexikJWTAuthenticationBundle. Doing things step by step and following the integration docs for fosuser and ldaptools and later the jwt docs I manage to acomplish the following:
FosUserBundle + LdapToolsBundle was successfull
Api Integration + FosUserBundle + LdapToolsBundle was successfull
Jwt + FosUserBundle + LdapToolsBundle on Api failed.
the problem is that I just can only log in against my database but not ldap.
in my database I have one user record which I created with fos command and any password(making shure the authentication is on ldap and not fosuser). So far so good. but once instroduced JWT the authentication is made by fosuser instead of ldap authentication guard. When I change the password with fos command I can get the token with out problems.
this is my config:
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
LdapTools\Bundle\LdapToolsBundle\Security\User\LdapUser: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
ldap:
id: ldap_tools.security.user.ldap_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/login
stateless: true
provider: fos_userbundle
anonymous: true
form_login:
check_path: /login
require_previous_session: false
username_parameter: username
password_parameter: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
guard:
authenticators:
- ldap_tools.security.ldap_guard_authenticator
logout: true
api:
pattern: ^/
stateless: true
lexik_jwt: ~
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: IS_AUTHENTICATED_FULLY }
My question is how can I get to work the ldap authenticator? I'll show the wanted workflow
Auth request is received (username and password)
The fosuser provider finds the user or user not found(bad credentials-->end)
The ldap authenticator guard authenticate the user against the domain server or bad credentials -->end
The user is logged in successfully and token is received
but I still getting bad credentials with a registered user in database and domain server (the user credential works)
Thanks in advance!

Diggin into the ldap_tools.security.ldap_guard_authenticator authenticator(namespace LdapTools\Bundle\LdapToolsBundle\Security;) I found this
public function getUser($credentials, UserProviderInterface $userProvider)
{
$domain = $this->ldap->getDomainContext();
try {
$credDomain = isset($credentials['ldap_domain']) ? $credentials['ldap_domain'] : '';
$this->switchDomainIfNeeded($credDomain);
$this->setLdapCredentialsIfNeeded($credentials['username'], $credentials['password'], $userProvider);
$user = $userProvider->loadUserByUsername($credentials['username']);
$this->userChecker->checkPreAuth($user);
return $user;
} catch (UsernameNotFoundException $e) {
$this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
} catch (BadCredentialsException $e) {
$this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
} catch (LdapConnectionException $e) {
$this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
} catch (\Exception $e) {
$this->hideOrThrow($e, $this->options['hide_user_not_found_exceptions']);
} finally {
$this->switchDomainBackIfNeeded($domain);
}
}
This method loads an user given an userprovider and some credentials.
So, the getUser relies in the userprovider in order to load any user, but since you are using fos_userbundle as userprovider for your api_login firewall you are, in fact, authenticating against your local database. Try using the ldap userprovider in your config instead.
Of course, doing this you will authenticate against the ldap server and not the flow you descibed above. To do so consider handling authentication by yourself, so you can handle the flow as you want.
Also you can do like this
customauthenticator and in your public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
you can check credentials against your ldap server with the service "#ldap_tools.ldap_manager"->authenticate($user, $password, &$errorMessage = false, &$errorNumber = false).

Here is my answer: Just force the auth after checking the ldap server.
public function onAuthenticationFailure(AuthenticationFailureEvent $event)//when auth fails on DB(Allways!!!)
{
$userToken = $event->getAuthenticationToken();
$username = $userToken->getUsername();
$password = $this->requestStack->getCurrentRequest()->get('password');
if ($this->ldapManager->authenticate($username, $password)) {//good credentials
$token = new UsernamePasswordToken($userToken, 'yes', "public", $userToken->getRoles());
$this->container->get('security.token_storage')->setToken($token);//set a token
$event = new InteractiveLoginEvent($this->requestStack->getCurrentRequest(), $token); //dispatch the auth event
$event->stopPropagation();
$this->container->get('event_dispatcher')->dispatch(SecurityEvents::INTERACTIVE_LOGIN,$event);
}
//symfony takes care of the response
}
So far this is the best answer I've found

Related

Symfony 6 Custom login strange behavior

I have a custom login system where using username/password against LDAP server. Based on the response i know that they are correct. The response is array with user details. I don't use the password hasher, the only information i store about the user is email/name which i recieve from the LDAP response.
My Authenticator
class CustomAuthenticator extends AbstractLoginFormAuthenticator
{
public function authenticate(Request $request): Passport
{
$uname = $request->request->get('uname', '');
$password = $request->request->get('password', '');
// check them against the LDAP server
$ldapData = $this->ldapChecker($uname, $password); // binds ldap server with username/password from login form. If the bind is success the ldap returns array containing user details (name, email etc)
if(is_array($ldapData)){
// if its array the submitted username/password are correct.
$email = $ldapData[0]['email'] // getting the user email from the ldap response
// here i'm checking if the username exist in the database if not i create new user
$checkUserExist = $this->user->findOneBy(['uname'=>$uname]);
if(!$checkUserExist){
// creating new user with the data from ldap response;
// storing name, uname, email
}
return new Passport(
new UserBadge($email, function($userIdentifier) {
$user = $this->user->findOneBy(['email' => $userIdentifier]);
if (!$user) {
throw new UserNotFoundException('passport fail');
}
return $user;
}),
new CustomCredentials(function($credentials, User $user) {
return true;
}, $password)
);
}
// here is logic to handle wrong username password
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return new RedirectResponse($this->urlGenerator->generate('app_home'));
}
My security.yaml
security:
# enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
role_hierarchy:
ROLE_ADMIN: ROLE_USER
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
custom_authenticator: App\Security\CustomAuthenticator
logout:
path: app_logout
# where to redirect after logout
# target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/login$, roles: PUBLIC_ACCESS }
- { path: ^/logout$, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER }
when#test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
The problem is sometimes when i open the app it loads user details from another user / or after loggin it shows details of another user or when i open incognito tab and open app it shows that i'm logged. I disabled the twig cache because i thought is cache problem but the problem persist.

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.

User object is not accessable in the controller in symfony

I'm trying to use auto login feature in symfony something like firing the login event and setting the user object. The user object is available in that controller but when I try to use other controller the user object says annon instead of showing the logged in user info
Controller A
private function autoLogin($request, $username)
{
$em = $this->getDoctrine()->getManager();
if (!$usr = $em->getRepository('AppBundle:User')->findOneBy(['username' => $username])) {
throw $this->createNotFoundException('User does not exist');
}
$token = new UsernamePasswordToken($usr, $usr->getPassword(), "secured_area", $usr->getRoles());
$this->get('security.token_storage')->setToken($token);
$loginEvent = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $loginEvent);
$user = $this->get('security.token_storage')->getToken()->getUser();
dump($user); // can see user object without any issue
if (!$this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
return $usr;
}
Controller B
public function editAction(Request $request)
{
$user = $this->get('security.token_storage')->getToken()->getUser();
print_r($user); // result is annon.
}
security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
providers:
doctrine_provider:
entity:
class: AppBundle:User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
anonymous: ~
provider: doctrine_provider
pattern: ^/
form_login:
login_path: security_login
check_path: security_login
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /login
access_control:
- { path: ^/.*, roles: IS_AUTHENTICATED_ANONYMOUSLY }
I'd assume that you're not using any security setup and the second controller is called after user refreshes the page.
The most likely problem is that your user is not persisted into the session. Symfony\Component\Security\Http\Firewall\ContextListener is responsible for that. If you have a look at onKernelResponse() method you can find out how it does it. Basically it gets token from token storage, serialize it and stores to the session. On the request it does opposite: gets token from session and puts it to token storage.
I'd suggest that you play with the configuration of firewall and set up something like this:
firewalls:
autologin:
pattern: /autologinUrl/
context: autologing
In this case context listener will be called doing session-related stuff and your code should work.

hwi_oauth Remember Me and IS_AUTHENTICATED_FULLY

I'm working on adding Oauth login to a Symfony2 site. I have the bundle working and configured with paypal and Facebook. I have remember me working. My issue is when a user comes back via remember me and tries to reauthenticate via oauth it tells me the accounts were connected fully but doesn't authenticate me at all. Re logging in with a user name and password works fine.
Config.yml
hwi_oauth:
# name of the firewall in which this bundle is active, this setting MUST be set
firewall_name: main
connect:
account_connector: app.provider.oauth
confirmation: true
resource_owners:
facebook:
type: facebook
client_id: %facebook_client_id%
client_secret: %facebook_client_secret%
scope: "email, public_profile"
infos_url: "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email,picture.type(large)"
paths:
email: email
options:
csrf: true
paypal:
type: paypal
client_id: %paypal_client_id%
client_secret: %paypal_client_secret%
scope: 'openid profile email'
options:
csrf: true
Security.yml
firewalls:
main:
pattern: ^/
anonymous: ~
oauth:
failure_path: /login
login_path: /login
check_path: /login
provider: fos_userbundle
remember_me: true
always_use_default_target_path: false
default_target_path: /login
resource_owners:
facebook: "/external-login/check-facebook"
paypal: "/external-login/check-paypal"
amazon: "/external-login/check-amazon"
oauth_user_provider:
service: app.provider.oauth
remember_me:
key: %secret%
lifetime: 31536000
path: /
domain: ~
always_remember_me: true
form_login:
login_path: /login
check_path: /login_check
success_handler: authentication_handler
failure_handler: authentication_handler
csrf_provider: form.csrf_provider
remember_me: true
logout: true
anonymous: true
switch_user: { role: ROLE_ALLOWED_TO_SWITCH, parameter: _new_user }
remember_me:
key: %secret%
lifetime: 31536000
path: /
domain: ~
always_remember_me: true
Routing.yml
hwi_oauth_redirect:
resource: "#HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /connect
hwi_oauth_login:
resource: "#HWIOAuthBundle/Resources/config/routing/login.xml"
prefix: /external-login/
hwi_oauth_connect:
resource: "#HWIOAuthBundle/Resources/config/routing/connect.xml"
prefix: /external-login/
facebook_login:
pattern: /external-login/check-facebook
paypal_login:
pattern: /external-login/check-paypal
Thanks!
My Own Answer (Solved)
so, I finally found the solution to my problem, maybe it will help others to solve it.
This is why the token failed to be authenticated.
For a returning user, on my home page, I called the is_granted('ROLE_ADMIN'), so the token was authenticated by the method authenticate of the AuthenticationManager, which was called in the AuthorizationChecker, as you can see below
final public function isGranted($attributes, $object = null)
{
if (null === ($token = $this->tokenStorage->getToken())) {
throw new AuthenticationCredentialsNotFoundException('The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.');
}
if ($this->alwaysAuthenticate || !$token->isAuthenticated()) {
$this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token));
}
if (!\is_array($attributes)) {
$attributes = array($attributes);
}
return $this->accessDecisionManager->decide($token, $attributes, $object);
}
The AuthenticationManager then uses the method authenticate of the OAuthProvider, which is one of its dependencies.This method looks like this:
* {#inheritdoc}
*/
public function authenticate(TokenInterface $token)
{
if (!$this->supports($token)) {
return;
}
// fix connect to external social very time
if ($token->isAuthenticated()) {
return $token;
}
/* #var OAuthToken $token */
$resourceOwner = $this->resourceOwnerMap->getResourceOwnerByName($token->getResourceOwnerName());
$oldToken = $token->isExpired() ? $this->refreshToken($token, $resourceOwner) : $token;
$userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());
try {
$user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);
} catch (OAuthAwareExceptionInterface $e) {
$e->setToken($oldToken);
$e->setResourceOwnerName($oldToken->getResourceOwnerName());
throw $e;
}
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('loadUserByOAuthUserResponse() must return a UserInterface.');
}
$this->userChecker->checkPreAuth($user);
$this->userChecker->checkPostAuth($user);
$token = new OAuthToken($oldToken->getRawToken(), $user->getRoles());
$token->setResourceOwnerName($resourceOwner->getName());
$token->setUser($user);
$token->setAuthenticated(true);
$token->setRefreshToken($oldToken->getRefreshToken());
$token->setCreatedAt($oldToken->getCreatedAt());
return $token;
}
As you can see, for a returning user, the OAuthToken is expired, so the OAuthProvider will try to refresh it
$oldToken = $token->isExpired() ? $this->refreshToken($token, $resourceOwner) : $token;
My issue was that my previous OAuthToken had a null refresh_token option. Although I had set the access_type to offline on my config.yml for the HWIOAuthBundle, it would not work, the refresh_token would still be null
hwi_oauth
resource_owners:
google:
type: google
client_id: "%google_app_id%"
client_secret: "%google_app_secret%"
scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/user.birthday.read"
options:
access_type: offline
prompt: consent
So why was my refresh_token always null? I fell on this other question on stack over flow
Not receiving Google OAuth refresh token
The answers tell you exactly why google does not send you a refresh_token.
Once you used the OAuth authentication without the refresh_token, google, will never send you a refresh token, unless you prompt the APi for consent.
This is why I added the prompt: consent to my configuration file.
So why did all of this generate a bug?
If I don't get the refresh_token from the google API, the userResponse of the OAuthProvider, which is based on the google API response, would have a null username attribute.
$userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());
Therefore, when the OAuthProvider tries to load a user thanks to the OAuthUserProvider, the username would be null so the OAuthUserProvider would not find the user.
$user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);
Therefore, I needed to get the refresh_token back from google in order to be able to have a correct userResponse with a non-empty username attribute.
Changing my configuration for hwi_oauth and adding these two options to google ressource_owners solved my problem
options:
access_type: offline
prompt: consent

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