Comments (16)
If the 2fa is not shown, what's shown instead? I've seen that your entire application is protected with - { path: ^/, role: ROLE_USER }
, so none of the pages should be accessible after login.
What happens when you navigate to /2fa
manually? Does it show the 2fa form?
from two-factor-bundle.
All the pages are accessible(based on dynamic roles I have) except /2fa
, that redirects me to access denied page I have.
My User entity role getter updated with custom roles I have in my permissions table, which gets assigned based on user group to different users. Because in user table by default all user will have ROLE_USER while admin will have ROLE_ADMIN, others will be from permissions and group relationship as shown in User
entity getRoles()
.
public function getRoles()
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
// Add group assinged roles
if ($this->typegroup) {
$permissions = $this->typegroup->getPermissions();
if ($permissions) {
foreach ($permissions as $permission) {
$roles[] = $permission->getRole();
}
}
}
return array_unique($roles);
}
I also have event listener which gets called on SecurityEvents::INTERACTIVE_LOGIN
My custom event subscriber class which adds all the permissions(ROLES) to admin/superadmin users.
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use App\Entity\Permission;
use App\Entity\FrontendConfig;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class ControllerSubscriber implements EventSubscriberInterface
{
private $tokenStorage;
private $em;
protected $userManager;
protected $authenticationContextProvider;
public function __construct(TokenStorageInterface $tokenStorage,
EntityManagerInterface $em,
UserManagerInterface $userManager,
SessionInterface $session)
{
$this->tokenStorage = $tokenStorage;
$this->em = $em;
$this->userManager = $userManager;
$this->session = $session;
}
public function onKernelController(ControllerEvent $event)
{
$frontEndConfig = $this->em->getRepository(FrontendConfig::class)->findAll();
$config_arr = [];
foreach($frontEndConfig as $config) {
$config_arr[$config->getAliasName()] = $config->getValue();
}
$this->session->set('frontEndConfig', $config_arr);
}
/**
* @param InteractiveLoginEvent $event
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$token = $this->tokenStorage->getToken();
$user = $token->getUser();
if ($user instanceof UserInterface) {
//Add all permissions for admin and superadmin users
$user_roles = $user->getRoles();
if (in_array('ROLE_ADMIN', $user_roles) || in_array('ROLE_SUPER_ADMIN', $user_roles) || in_array('ROLE_TEACHER', $user_roles)) {
if (in_array('ROLE_TEACHER', $user_roles)) {
$permissionsNeedToBeMerged = [ 'ROLE_CREATE_MEETING',
'ROLE_VIEW_AGENDA_MEETINGS',
'ROLE_VIEW_AGENDA_HOLIDAYS',
'ROLE_VIEW_AGENDA_BREAKS'
];
$user_roles = array_merge($user_roles, $permissionsNeedToBeMerged);
} else {
$permissions = $this->em->getRepository(Permission::class)->findAll();
foreach ($permissions as $permission) {
$user_roles[] = $permission->getRole();
}
}
// there didn't seem to be an easier way to grab the provider key,
// so using bound closure to retrieve it
$providerKeyGetter = function($token) {
return $token->providerKey;
};
$boundProviderKeyGetter = \Closure::bind($providerKeyGetter, null, $token);
// check & load roles for user here if necessary
$this->tokenStorage->setToken(
new UsernamePasswordToken(
$user,
$token->getCredentials(),
$boundProviderKeyGetter($token), //main == firewall setting
array_unique($user_roles)
)
);
}
}
}
public static function getSubscribedEvents()
{
return [
ControllerEvent::class => 'onKernelController',
SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
];
}
}
from two-factor-bundle.
ControllerSubscriber::onSecurityInteractiveLogin
looks like a problem to me, It replaces the security token (which should be a TwoFactorToken
at that point) with a UsernamePasswordToken
. This will skip the 2fa process, which requires the TwoFactorToken
to be present.
I'd suggest you change your code in ControllerSubscriber
from:
if ($user instanceof UserInterface)
to:
if ($user instanceof UserInterface && $token instanceof UsernamePasswordToken)
This will make sure:
- the TwoFactorToken is kept on login
- your code is still executed, once 2fa was completed
from two-factor-bundle.
Alternatively, instead of using SecurityEvents::INTERACTIVE_LOGIN
, you could also subscribe to the AuthenticationEvents::AUTHENTICATION_SUCCESS
event, which is only dispatched after 2fa was completed.
from two-factor-bundle.
Wow, super fast. Thanks for the replies. It now redirects to 2fa
but could not load form, giving below error.
Unable to find template "security/2fa_form.html.twig"
from two-factor-bundle.
Well, that is the template you have configured in config\packages\scheb_two_factor.yaml
:
scheb_two_factor:
security_tokens:
template: security/2fa_form.html.twig # Template used to render the authentication form
Make sure the file is there ;)
from two-factor-bundle.
Ohh, I forgot to add template thought using default you provided. Add now (had to remove logout and cancel link as I have different path). So now it redirects to form but no email process.
from two-factor-bundle.
I have MAILER_DSN
set which uses my Mailgun Account in env.
MESSENGER_TRANSPORT_DSN=doctrine://default
messagener:consume
command already run on cmd and processing all other emails. Messenger table not updated with any email and no data on command verbose log.
from two-factor-bundle.
This method should be called to send the email. https://github.com/scheb/two-factor-bundle/blob/master/Mailer/AuthCodeMailer.php#L33
Once that method is called, it's the responsibility of your email setup to send-out the mail.
from two-factor-bundle.
Yes, that method gets called. Thank you very much for your help. I will follow documentation for creating Custom Mailer from there I will need to send email using Mailer Interface.
from two-factor-bundle.
Replacing
if ($user instanceof UserInterface)
with
if ($user instanceof UserInterface && $token instanceof UsernamePasswordToken)
redirects properly to /2fa
and login works with auth code after implementing custom Mailer service.
Updated event you suggested from
SecurityEvents::INTERACTIVE_LOGIN
to
AuthenticationEvents::AUTHENTICATION_SUCCESS
However, instance of $token
is not UsernamePasswordToken
but \Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorToken
in AuthenticationEvents::AUTHENTICATION_SUCCESS
listener.
Hence, my code to update roles for user fails, due to if
condition check fail as mentioned above.
Replacing
if ($user instanceof UserInterface && $token instanceof UsernamePasswordToken)
with
if ($user instanceof UserInterface && $token instanceof \Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorToken)
updates UsernamePasswordToken
User instance roles but it does not update actual UsernamePasswordToken
which I see after login in symfony profiler.
Updated subscriber class:
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use App\Entity\Permission;
use App\Entity\FrontendConfig;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class ControllerSubscriber implements EventSubscriberInterface
{
private $tokenStorage;
private $em;
protected $userManager;
protected $authenticationContextProvider;
public function __construct(TokenStorageInterface $tokenStorage,
EntityManagerInterface $em,
UserManagerInterface $userManager,
SessionInterface $session)
{
$this->tokenStorage = $tokenStorage;
$this->em = $em;
$this->userManager = $userManager;
$this->session = $session;
}
public function onKernelController(ControllerEvent $event)
{
$frontEndConfig = $this->em->getRepository(FrontendConfig::class)->findAll();
$config_arr = [];
foreach($frontEndConfig as $config) {
$config_arr[$config->getAliasName()] = $config->getValue();
}
$this->session->set('frontEndConfig', $config_arr);
}
/**
* @param AuthenticationSuccessEvent $event
*/
public function onAuthenticationSuccess(AuthenticationSuccessEvent $event)
{
$token = $this->tokenStorage->getToken();
if ($token) {
$user = $token->getUser();
if ($user instanceof UserInterface && $token instanceof \Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorToken) {
//Add all permissions for admin and superadmin users
$user_roles = $user->getRoles();
if (in_array('ROLE_ADMIN', $user_roles) || in_array('ROLE_SUPER_ADMIN', $user_roles) || in_array('ROLE_TEACHER', $user_roles)) {
if (in_array('ROLE_TEACHER', $user_roles)) {
$permissionsNeedToBeMerged = [ 'ROLE_CREATE_MEETING',
'ROLE_VIEW_AGENDA_MEETINGS',
'ROLE_VIEW_AGENDA_HOLIDAYS',
'ROLE_VIEW_AGENDA_BREAKS'
];
$user_roles = array_merge($user_roles, $permissionsNeedToBeMerged);
} else {
$permissions = $this->em->getRepository(Permission::class)->findAll();
foreach ($permissions as $permission) {
$user_roles[] = $permission->getRole();
}
}
// there didn't seem to be an easier way to grab the provider key,
// so using bound closure to retrieve it
$providerKeyGetter = function($token) {
return $token->providerKey;
};
$boundProviderKeyGetter = \Closure::bind($providerKeyGetter, null, $token);
// check & load roles for user here if necessary
$this->tokenStorage->setToken(
new UsernamePasswordToken(
$user,
$token->getCredentials(),
$boundProviderKeyGetter($token), //main == firewall setting
array_unique($user_roles)
)
);
}
}
}
}
public static function getSubscribedEvents()
{
return [
ControllerEvent::class => 'onKernelController',
AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess',
];
}
}
from two-factor-bundle.
Please use $event->getAuthenticationToken()
instead of $this->tokenStorage->getToken()
. The one in the token storage is not necessary updated at that point.
And please note that AuthenticationEvents::AUTHENTICATION_SUCCESS
is triggered twice. First directly after login (with a TwoFactorToken
) and then a second time when 2fa was completed (with the original UsernamePasswordToken
). Only on that second call your code should execute.
Btw., the way you're injecting these extra roles, I don't think this is the best way to do it, messing around with the security token. Overwriting the security token is a bit hacky, I believe the security token should only be modified by the security system. The more safe way to do this would probably be to write your own user provider, which adds these extra roles to the user when it's loaded from the database.
from two-factor-bundle.
Thanks for the help and suggestions. Instead of replacing security token, updated User
roles with UserManager
interface and everything works fine.
$user->setRoles(array_unique($user_roles));
$this->userManager->updateUser($user);
from two-factor-bundle.
Sounds good to me 👍
from two-factor-bundle.
@hardiksinh Can we close this?
from two-factor-bundle.
@hardiksinh Can we close this?
Yes
from two-factor-bundle.
Related Issues (20)
- Use multiple interfaces, one with JWT and API HOT 8
- Resent email authentication code HOT 8
- Type error: Too few arguments to function Scheb\TwoFactorBundle\Controller\FormController::__construct(), 0 passed HOT 2
- LogoutHandlerInterface is deprecated since Symfony 5.1 HOT 6
- No Two factor form after logging in (no redirect) HOT 7
- User Deprecated HOT 1
- Authenticate two factor in one request (with json_login) HOT 4
- Called providers session value is never unset HOT 4
- How to use the two factor auth in combination with manual authentication HOT 3
- Doesn't work in PROD works in DEV HOT 2
- 2FA by api + jwt HOT 8
- FatalThrowableError in RememberMeServicesDecoratorCompilerPass HOT 4
- impossible to go through page 2fa during login HOT 8
- How to change default path 2fa HOT 1
- Infinite login loop when updating user credentials HOT 2
- Login via email failed HOT 7
- Not prompted for Google Authenticator code on login HOT 14
- 2fa form is not being shown HOT 16
- Session id changes after 2fa succeed. And UsernamePasswordToken cannot be loaded from Redis. HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from two-factor-bundle.