FOSUserBundle and HWIOAuthBundle registration - php

I am trying to combine FOSUserBundle and HWIOAuthBundle following articles like https://gist.github.com/danvbe/4476697. However, I do not want the automatic registration of OAuth2 authenticated new users: additional information should be provided by the user.
Desired result
I would for example want the following information for a registered user:
(Username, although I'd rather just use e-mail)
Display name (required)
Profile picture (required)
Email address (required if no Facebook-id)
Password (required if no Facebook-id)
Facebook-id (required if no email address)
Now, when a user authenticates through Facebook and the user does not exist yet, I want a registration form to fill out the missing information (display name and profile picture). Only after this, the new FOSUser should be created.
In most tutorials, fields like Profile picture and Email address are automatically populated with the Facebook information. This is not always desirable nor possible.
Also, think of things like accepting Terms of Agreement and rules you wish to show before the user is created.
Possible approaches
A solution would be, I think, to create a new sort-of AnonymousToken, the OAuthenticatedToken, which holds the relevant OAuth2 information but does not count an authenticaton. Then, make all pages check for this kind of authentication and let other pages redirect to OAuth-registration-page. However, this seems an unnecessarily complicated solution to me.
Another solution would probably be to write the code from scratch and not use the two bundles mentioned. I really hope this is not necessary.
Q: How can I insert the registration-completion-code in the rest of the login flow?
(I'd love to share some code, but since it's the very concept I need help at, I don't have a lot to show.)
Edit: Solution
Following Derick's adivce, I got the basics working like this:
The Custom user provider saves the information (sadly, no access to the raw token so I cannot yet log the user in after registering):
class UserProvider extends FOSUBUserProvider {
protected $session;
public function __construct(Session $session, UserManagerInterface $userManager, array $properties) {
$this->session = $session;
parent::__construct( $userManager, $properties );
}
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
try {
return parent::loadUserByOAuthUserResponse($response);
}
catch ( AccountNotLinkedException $e ) {
$this->session->set( 'oauth.resource', $response->getResourceOwner()->getName() );
$this->session->set( 'oauth.id', $response->getResponse()['id'] );
throw $e;
}
}
}
Custom failure handler:
<?php
// OAuthFailureHandler.php
class OAuthFailureHandler implements AuthenticationFailureHandlerInterface {
public function onAuthenticationFailure( Request $request, AuthenticationException $exception) {
if ( !$exception instanceof AccountNotLinkedException ) {
throw $exception;
}
return new RedirectResponse( 'fb-register' );
}
}
Both are registered as a service:
# services.yml
services:
app.userprovider:
class: AppBundle\Security\Core\User\UserProvider
arguments: [ "#session", "#fos_user.user_manager", {facebook: facebookID} ]
app.oauthfailurehandler:
class: AppBundle\Security\Handler\OAuthFailureHandler
arguments: ["#security.http_utils", {}, "#service_container"]
And configured in security config:
# security.yml
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
main:
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /login
check_path: /login_check
default_target_path: /profile
oauth:
login_path: /login
check_path: /login_check
resource_owners:
facebook: hwi_facebook_login
oauth_user_provider:
service: app.userprovider
failure_handler: app.oauthfailurehandler
anonymous: true
logout:
path: /logout
target: /login
At /fb-register, I let the user enter a username and save the user myself:
/**
* #Route("/fb-register", name="hwi_oauth_register")
*/
public function registerOAuthAction(Request $request) {
$session = $request->getSession();
$resource = $session->get('oauth.resource');
if ( $resource !== 'facebook' ) {
return $this->redirectToRoute('home');
}
$userManager = $this->get('fos_user.user_manager');
$newUser = $userManager->createUser();
$form = $this->createForm(new RegisterOAuthFormType(), $newUser);
$form->handleRequest($request);
if ( $form->isValid() ) {
$newUser->setFacebookId( $session->get('oauth.id') );
$newUser->setEnabled(true);
$userManager->updateUser( $newUser );
try {
$this->container->get('hwi_oauth.user_checker')->checkPostAuth($newUser);
} catch (AccountStatusException $e) {
// Don't authenticate locked, disabled or expired users
return;
}
$session->remove('oauth.resource');
$session->remove('oauth.id');
$session->getFlashBag()
->add('success', 'You\'re succesfully registered!' );
return $this->redirectToRoute('home');
}
return $this->render( 'default/register-oauth.html.twig', array(
'form' => $form->createView()
) );
}
The user is not logged in afterwards, which is too bad. Also, the normal fosub functionality (editing profile, changing password) does not work out of the box anymore.
I'm simply using the username as the displayname, not sure why I didn't see that before.

Step 1:
Create your own user provider. Extend the OAuthUserProvider and customize to your needs. If the user successfully oauthed in, throw a specific exception (probably the accountnotlinkedException) and toss all relevant data about the login somewhere
Step 2:
Create your own authentication failure handler. Check to make sure the error being thrown is the specific one you threw in step 1.
In here you will redirect to your fill in additional info page.
This is how to register you custom handlers:
#security.yml
firewall:
main:
oauth:
success_handler: authentication_handler
failure_handler: social_auth_failure_handler
#user bundle services.yml (or some other project services.yml)
services:
authentication_handler:
class: ProjectName\UserBundle\Handler\AuthenticationHandler
arguments: ["#security.http_utils", {}, "#service_container"]
tags:
- { name: 'monolog.logger', channel: 'security' }
social_auth_failure_handler:
class: ProjectName\UserBundle\Handler\SocialAuthFailureHandler
arguments: ["#security.http_utils", {}, "#service_container"]
tags:
- { name: 'monolog.logger', channel: 'security' }
Step 3:
Create your fill in additional info page. Pull all relevant data that you stored back in step 1 and create the user if everything checks out.

Related

Symfony custom logout

I am trying to create a logout feature which when clicked from the below (example) redirects to the necessary page.
Example:
If anybody clicks logout with website beginning with aaa.io/user -> go to aaa.io/login.
If anybody clicks logout with website beginning with aaa.io/dev/{id} -> go to aaa.io/home/{id}
How to create two logout feature which will redirect to two seperate pages? I have tried with first example and works fine.I heard we can do it using Symfony firewall but unable to get it.
#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'
App\Entity\Dimitry:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\Dimitry
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
custom_authenticator: App\Security\LoginAuthenticator
logout:
path: app_logout
target: app_login
//Security Controller
#[Route(path: '/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('app_home');
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
#[Route(path: '/logout', name: 'app_logout')]
public function logout()
{
return $this->redirectToRoute('app_login');
//throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
The logout method is never called because it's intercepted by the logout key on your firewall. So the code lines in the public function logout won't be executed.
IMO, you could use events :
Create a subscriber,
Subscribe on LogoutEvent::class,
Analyze the request provided by the logout event
Use it to determine the route,
Catch the response provided by the logout event,
Use the UrlGenerator to redirect user,
Update the response to redirect to the corresponding route
Documentation provides a very good example, you can use as a template for your logic. Your subscriber could be like this one:
// src/EventListener/LogoutSubscriber.php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class LogoutSubscriber implements EventSubscriberInterface
{
public function __construct(
private UrlGeneratorInterface $urlGenerator
) {
}
public static function getSubscribedEvents(): array
{
//2 - Subscribe to LogoutEvent
return [LogoutEvent::class => 'onLogout'];
}
public function onLogout(LogoutEvent $event): void
{
// get the security token of the session that is about to be logged out
$token = $event->getToken();
// 3. get the current request
$request = $event->getRequest();
// 4. Your own logic to analyze the URL
$route = 'homepage';//default route
if (...) {
$route = 'URL1';
}
if (...) {
$route = 'URL2';
}
// 5. get the current response, if it is already set by another listener
$response = $event->getResponse();
// configure a custom logout response to the homepage
$response = new RedirectResponse(
$this->urlGenerator->generate($route),
RedirectResponse::HTTP_SEE_OTHER
);
$event->setResponse($response);
}
}

Symfony 4 Retreiving the User's Email on Failed Login

I have created an event listener to increment a failed login count when a user's login attempt fails but am unable to fetch the username associated with the request, which in this case is the user's email.
class LoginFailureListener
{
private $requestStack;
private $entityManager;
public function __construct(RequestStack $requestStack, EntityManagerInterface $entityManager)
{
$this->requestStack = $requestStack;
$this->entityManager = $entityManager;
}
public function onAuthenticationFailure(AuthenticationFailureEvent $event)
{
$email = $event->getAuthenticationToken()->getUsername();
dump($email);
The value of $email is an empty string... I have seen other examples where getUsername() seems to return the expected value.
In my User model I have defined the following:
/**
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
security.yaml:
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
main:
anonymous: true
guard:
authenticators:
- App\Security\LoginFormAuthenticator
form_login:
login_path: login
check_path: login
username_parameter: "email"
password_parameter: "password"
Is there another place I need to configure getUsername so that it returns the user's identifier (email)?
The following yielded the required information:
$email = $event->getAuthenticationToken()->getCredentials()['email'];
There's no other place where you must configure getUsername(), but in your Entity - and that seems to be Ok.
My suggestion (and $200 bet), is that maybe there is not authenticationToken, because precisely the User failed to enter valid credentials.
However, even anonymous users have a session token, so you could try this:
Inject ContainerInterface $container in your listener's constructor, then try
$token = $this->container->get('security.token_storage')->getToken();
$user = $token->getUser();
$email = $this->container->get('session')->get('_security.last_username');
dump($token, $user, $email);
If there's no email, then it's probably because it was not set in the session right after the login attempt.
Symfony usually does this right after a login, with any class that extends the AbstractFormLoginAuthenticator, in method getCredentials()
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['email']
);
See https://symfony.com/doc/current/security/form_login_setup.html
Hope this helps you mate. Best of luck.
EDIT : your security.yaml seems Ok too, but make sure you have defined your form login below, according to these guidelines:
form_login:
login_path: login # or whatever your path is
check_path: login # or whatever your path is
username_parameter: login[email] # these fields need to be as they appear in the html form
password_parameter: login[password]
default_target_path: index # or whatever your path is
provider: app_user_provider

Symfony 3 login is instantly forgotten unless "Remember Me" is used

We've updated a base project from Symfony 2.8 to 3.4. This has largely gone well, blah blah, but I've noticed quite an important issue.
It seems that logging in is instantly forgotten after the request for "check_path" has completed unless the user chooses the "Remember Me" option. -- We do not provide this option for management interfaces to ensure the user has authenticated properly, therefore the management interface can't be accessed at all.
Request flow goes as shown in Symfony profiler:
Attempt to access firewalled route.
401 response showing login form with Anonymous token.
Submit login form.
302 response with UsernamePassword token. -- This shows the username and password has been accepted.
Redirected to original firewalled route.
200 response with Anonymous token. -- UsernamePassword token has gone!
This response does not appear in the web browser's network debugger.
Redirected to login form again.
401 response showing login form with Anonymous token.
Contents of "app/config/security.yml":
security:
encoders:
App\UserBundle\Entity\User:
algorithm: bcrypt
cost: 16
providers:
local_db:
entity: { class: AppUserBundle:User }
firewalls:
dev:
pattern: ^/(_(profiler|wdt))/
security: false
assets:
pattern: ^/(css|images|js)/
security: false
admin:
pattern: ^/admin
provider: local_db
anonymous: ~
logout_on_user_change: true
form_login:
csrf_token_generator: security.csrf.token_manager
login_path: user_admin_login
check_path: user_admin_login
default_target_path: dashboard
use_forward: true
use_referer: true
logout:
path: user_admin_logout
target: dashboard
handler: auth_listener
invalidate_session: true
switch_user:
role: ROLE_TOP_ADMIN
parameter: _login_as_user
# remember_me:
# secret: "%secret%"
front:
pattern: ^/
provider: local_db
anonymous: ~
logout_on_user_change: true
form_login:
csrf_token_generator: security.csrf.token_manager
# login_path should be "user_account_login" or "user_account_auth" depending on which view you want.
login_path: user_account_login
check_path: user_account_login
default_target_path: user_account
use_forward: true
use_referer: true
logout:
path: user_account_logout
target: home
handler: auth_listener
invalidate_session: true
switch_user:
role: ROLE_ADMIN
parameter: _login_as_user
remember_me:
secret: "%secret%"
access_control:
- { path: ^/admin/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: [ROLE_CONTRIBUTOR, ROLE_EDITOR, ROLE_ADMIN, ROLE_TOP_ADMIN] }
- { path: ^/account/auth$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/account/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/account/register$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/account/forgot_password$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/account/change_password$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/account, roles: [ROLE_USER, ROLE_CONTRIBUTOR, ROLE_EDITOR, ROLE_ADMIN, ROLE_TOP_ADMIN] }
# Change the below "IS_AUTHENTICATED_ANONYMOUSLY" to "ROLE_USER" if this is going to be a private website.
# This will ensure users have to login on the landing page.
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Routes:
user_admin_login points to "/admin/login"
user_admin_logout points to "/admin/logout"
user_account_auth points to "/account/auth".
user_account_login points to "/account/login".
user_account_logout points to "/account/logout".
I'm wondering if this is cookie related. I did notice that the Symfony session ID cookie ("webapp" below) value is changing after logging in, but it does remain consistent between page navigations elsewhere. It only changes twice when submitting the login form. Using the response references above:
Attempt to access firewalled route:
"webapp" value is "h76n9kcra43stmjb5accnqlg70itnavf" on 401 response.
Submit login form:
"webapp" value is "15iscbl51k2mjs14bck5m54f4m8qhtme" on 302 response.
Redirected to firewalled route:
"webapp" value is "ciibpf8h54u2vp3gdi31bvdm5oj3r3ts" on 200 response.
Redirected to login form:
"webapp" value is "ciibpf8h54u2vp3gdi31bvdm5oj3r3ts" on 401 response.
Contents of "app/config/config.yml" session section:
session:
storage_id: "session.storage.native"
handler_id: "session.handler.native_file"
name: "webapp"
cookie_lifetime: 604800
gc_divisor: 10
gc_probability: 1
gc_maxlifetime: 14400
save_path: "%kernel.root_dir%/../var/sessions"
I tried using different web browsers with very default cookie settings in case something was up with Chrome, no different though.
If it helps, when successfully logged in with the "remember me" option ticked, the token is a RememberMeToken -- not a UsernamePasswordToken.
Please let me know if any further information is required.
The goal here is to be able to login without needing a "remember me" option enabled.
Edit: User entity model
As requested, here is some detail about the user entity model. It's quite big (2016 lines) so I'll just paste in the parts relevant to Symfony's user interface.
Declaration
class User implements AdvancedUserInterface, UserPermissionInterface, DataContentEntityInterface, \Serializable
Interfaces UserPermissionInterface and DataContentEntityInterface are custom for our application. (Irrelevant.)
Serializable relevant parts
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
return serialize([
$this->id,
$this->userName,
$this->email,
$this->password,
// $this->salt,
]);
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list(
$this->id,
$this->userName,
$this->email,
$this->password,
// $this->salt,
) = unserialize($serialized, ["allowed_classes" => false]);
}
UserInterface relevant parts
/**
* #inheritDoc
*/
public function getSalt()
{
return null;
}
/**
* #inheritDoc
*/
public function getRoles()
{
if (!$this->group) {
return [];
}
$rolesArray = array();
foreach ($this->getGroup()->getPermissions() as $k => $permission) {
$role = strtoupper($permission);
$role = str_replace('.', '_', $role);
$role = sprintf("ROLE_%s", $role);
$rolesArray[$k] = $role;
}
$rolesArray[] = $this->getGroup()->etRole();
// If user is top admin, also give admin group
if ($this->getGroup()->getRole() === "ROLE_TOP_ADMIN") {
$rolesArray[] = "ROLE_ADMIN";
}
return $rolesArray;
}
/**
* #inheritDoc
*/
public function eraseCredentials()
{
}
/**
* Get userName
*
* #return string
*/
public function getUserName()
{
return $this->userName;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
AdvancedUserInterface relevant parts
public function isAccountNonExpired()
{
if (!$this->expires) {
return true;
}
if (new \DateTime() <= $this->expires) {
return true;
}
return false;
}
public function isAccountNonLocked()
{
return $this->status === self::STATUS_VERIFIED;
}
public function isCredentialsNonExpired()
{
if (!$this->passwordExpires) {
return true;
}
if (new \DateTime() <= $this->passwordExpires) {
return true;
}
return false;
}
public function isEnabled()
{
return $this->isAccountNonLocked() && !$this->activationCode;
}
Entity repository UserRepository declaration
class UserRepository extends EntityRepository implements UserLoaderInterface
Function to load user
/**
* UserLoaderInterface
* #param string $userName User to look for
* #return User|null User entity, or null if not found
*/
public function loadUserByUsername($userName)
{
$qb = $this
->createQueryBuilder("u")
->where("u.userName = :userName OR u.email = :userName")
->setParameter("userName", $userName)
->andWhere("u.status != :statusDeleted")
->setParameter("statusDeleted", User::STATUS_DELETED)
->andWhere("u.status = :statusVerified")
->setParameter("statusVerified", User::STATUS_VERIFIED)
->orderBy("u.status", "DESC")
->addOrderBy("u.group", "ASC")
->addOrderBy("u.created", "ASC")
->setMaxResults(1)
;
$query = $qb->getQuery();
try {
// The Query::getSingleResult() method throws an exception
// if there is no record matching the criteria.
$user = $query->getSingleResult();
} catch (NoResultException $e) {
throw new UsernameNotFoundException(sprintf("Unable to find an active user identified by \"%s\".", $username), 0, $e);
} catch (NonUniqueResultException $e) {
throw new UsernameNotFoundException(sprintf("Unable to find a unique active user identified by \"%s\".", $username), 0, $e);
}
return $user;
}
This function works fine. A valid user entity is definitely returned.
In your security.yml, remove:
logout_on_user_change: true
or set it to false.
This will solve the instant logout issue, though it will also bypass a security feature of Symfony.
It seems as if something in the serialize() and unserialize() isn't matching, and Symfony then logs the user out as a precaution. With AdvancedUserInterface, Symfony also checks that the AdvancedUserInterface methods match too. If you have anything else going on in those methods which could cause the users to not match (like some bespoke roles management), that could be triggering the logout. To debug, I would suggest returning true in each of the AdvancedUserInterface methods, then re-adding your functionality until the logout gets triggered.
From Symfony's documentation:
If you're curious about the importance of the serialize() method inside
the User class or how the User object is serialized or deserialized,
then this section is for you. If not, feel free to skip this.
Once the user is logged in, the entire User object is serialized into
the session. On the next request, the User object is deserialized.
Then, the value of the id property is used to re-query for a fresh
User object from the database. Finally, the fresh User object is
compared to the deserialized User object to make sure that they
represent the same user. For example, if the username on the 2 User
objects doesn't match for some reason, then the user will be logged
out for security reasons.
Even though this all happens automatically, there are a few important
side-effects.
First, the Serializable interface and its serialize() and
unserialize() methods have been added to allow the User class to be
serialized to the session. This may or may not be needed depending on
your setup, but it's probably a good idea. In theory, only the id
needs to be serialized, because the refreshUser() method refreshes the
user on each request by using the id (as explained above). This gives
us a "fresh" User object.
But Symfony also uses the username, salt, and password to verify that
the User has not changed between requests (it also calls your
AdvancedUserInterface methods if you implement it). Failing to
serialize these may cause you to be logged out on each request. If
your user implements the EquatableInterface, then instead of these
properties being checked, your isEqualTo() method is called, and you
can check whatever properties you want. Unless you understand this,
you probably won't need to implement this interface or worry about it.
Resolved this with insight from #jedge.
Turns out the logout_on_user_change option being true was the cause. Changing this to false resolved the issue. -- I'm not sure what this does as there is little documentation on it, and worryingly this has become true by default in Symfony 4...
Other things we tried were the temporary removal of CSRF, forwarding, and logout event. -- None of these turned out to conflict. We were also able to login programmatically by manually creating a token for a specific user and dispatching an InteractiveLoginEvent, which led us on to the firewall configuration.

Symfony 2.6 Security Login redirects back to login page on successful login

I am currently setting up a login system with a custom User Provider for Symfony2. I made the User and Provider classes, and the login system works fine with "manual" test variables. For example, this is my provider with test user data that always has a successful login:
<?php
namespace SoftAltern\DashboardBundle\Security\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class WebserviceUserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$userData = [
'username' => 'test',
'password' => 'testpass',
'roles' => [],
];
if ($userData) {
return new WebserviceUser($userData['username'], $userData['password'], '', $userData['roles']);
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'SoftAltern\DashboardBundle\Security\User\WebserviceUser';
}
}
This works fine obviously. However, when I try adding my own check that returns an array built in the same format, it just goes back to the login page. Here is that example:
<?php
namespace SoftAltern\DashboardBundle\Security\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use SoftAltern\DashboardBundle\Helper\I5ToolkitHelper;
class WebserviceUserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$request = Request::createFromGlobals();
$toolkit = new I5ToolkitHelper;
$userData = $toolkit->checkUserData($username, $request->request->get('_password'));
if ($userData) {
return new WebserviceUser($userData['username'], $userData['password'], '', $userData['roles']);
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'SoftAltern\DashboardBundle\Security\User\WebserviceUser';
}
}
I believe that the issue lies in using the request to get the password. However, in my current system (an AS/400 IBM iSeries), there is no way to just grab the password based on the username. So what I'm trying to do is use the i5ToolKit, check the credentials, and return the userData based on a successful iSeries login or not. It returns the array fine, but still just redirects back to the login page on success.
The reason I believe it has something to do with the request is because if I comment that part out and manually submit a valid username and password to my I5ToolKitHelper, everything works as expected.
I have also already tried the way they suggested using request in this post, but it didn't help either.
Here is my security.yml:
# you can read more about security in the related section of the documentation
# http://symfony.com/doc/current/book/security.html
security:
# http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
encoders:
SoftAltern\DashboardBundle\Security\User\WebserviceUser: plaintext
# http://symfony.com/doc/current/book/security.html#hierarchical-roles
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
webservice:
id: webservice_user_provider
# the main part of the security, where you can set up firewalls
# for specific sections of your app
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# the login page has to be accessible for everybody
login:
pattern: ^/login$
security: false
# secures part of the application
soft_altern_dashboard_secured_area:
pattern: ^/
# it's important to notice that in this case _demo_security_check and _demo_login
# are route names and that they are specified in the AcmeDemoBundle
form_login:
check_path: soft_altern_dashboard_login_check
login_path: soft_altern_dashboard_login
logout:
path: soft_altern_dashboard_logout
target: soft_altern_dashboard_homepage
#anonymous: ~
#http_basic:
# realm: "Secured Demo Area"
# with these settings you can restrict or allow access for different parts
# of your application based on roles, ip, host or methods
# http://symfony.com/doc/current/cookbook/security/access_control.html
access_control:
#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
Any help would be greatly appreciated!
Edit: I error logged the password during one login and it looks like it hits the login check twice, and on the second go around the password is empty. This behavior might be for security reasons. I'm not sure if I have a setting wrong that might be causing this.
There could be a number of related things. The page that Symfony redirects to is normally stored into session. You can change it. But first, try to add under the form_login setting the following option:
form_login:
#...
use_referer: true

symfony2 No redirect on restricted areas

I have my security file configured as follows:
security:
...
pattern: ^/[members|admin]
form_login:
check_path: /members/auth
login_path: /public/login
failure_forward: false
failure_path: null
logout:
path: /public/logout
target: /
Currently if I access the members url without authenticating it redirects me to /public/login but I dont want it to redirect. I'm mainly responding with json on my controllers so I just want to show a warning on the restricted url such as {"error": "Access denied"}. If I take out the login_path: /public/login code it redirects to a default url /login. How do I do to stop it from redirecting?
You need to create a Listener and then trigger your response. My solution is based on - https://gist.github.com/xanf/1015146
Listener Code --
namespace Your\NameSpace\Bundle\Listener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class AjaxAuthenticationListener
{
/**
* Handles security related exceptions.
*
* #param GetResponseForExceptionEvent $event An GetResponseForExceptionEvent instance
*/
public function onCoreException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
$request = $event->getRequest();
if ($request->isXmlHttpRequest()) {
if ($exception instanceof AuthenticationException || $exception instanceof AccessDeniedException || $exception instanceof AuthenticationCredentialsNotFoundException) {
$responseData = array('status' => 401, 'msg' => 'User Not Authenticated');
$response = new JsonResponse();
$response->setData($responseData);
$response->setStatusCode($responseData['status']);
$event->setResponse($response);
}
}
}
}
You need to create a service for the listener --
e_ent_int_baems.ajaxauthlistener:
class: Your\NameSpace\Bundle\Listener\AjaxAuthenticationListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onCoreException, priority: 1000 }
You can do like I did:
in security.yml
firewalls:
administrators:
pattern: ^/
form_login:
check_path: _security_check
login_path: _security_login
logout: true
security: true
anonymous: true
access_denied_url: access_denied
in routing.yml
access_denied:
path: /error403
defaults :
_controller: FrameworkBundle:Template:template
template: 'DpUserBundle:Static:error403.html.twig'
simply add to firewall section *access_denied_url* param
See this page for the full security.yml configuration reference. Also, this is an even better reference with explanations of each key.
I'd suggest creating your own listener class to handle returning JSON when a User needs to login. Example: https://gist.github.com/1015146

Categories