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);
}
}
Related
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.
In Symfony, after a user successfully log out, how to display a success message like "you have successfully logged out" ?
1) Create a new service to handle the logout success event.
In services.yml add the service:
logout_success_handler:
class: Path\To\YourBundle\Services\LogoutSuccessHandler
arguments: ['#security.http_utils']
And add the class, replacing /path/to/your/login with the url of your login page (in the last line of the controller):
<?php
namespace Path\To\YourBundle\Services;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
protected $httpUtils;
protected $targetUrl;
/**
* #param HttpUtils $httpUtils
*/
public function __construct(HttpUtils $httpUtils)
{
$this->httpUtils = $httpUtils;
$this->targetUrl = '/path/to/your/login?logout=success';
}
/**
* {#inheritdoc}
*/
public function onLogoutSuccess(Request $request)
{
$response = $this->httpUtils->createRedirectResponse($request, $this->targetUrl);
return $response;
}
}
2) Configure your security.yml to use the custom LogoutSuccessHandler just created:
firewalls:
# ...
your_firewall:
# ...
logout:
# ...
success_handler: logout_success_handler
3) In the twig template of your login page add:
{% if app.request.get('logout') == "success" %}
<p>You have successfully logged out!</p>
{% endif %}
Imho, there is a definitively simpler way. In security.yaml, define a route to redirect to after logout thanks to the target key :
security:
firewalls:
main:
[...]
logout:
path: /logout
target: /logout_message
Then in a controller (SecurityController.php is fine for this), define this action, wich only add the flash message and then redirect where you want (home in this exemple) :
/**
* #Route("/logout_message", name="logout_message")
*/
public function logoutMessage()
{
$this->addFlash('success', "You've been disconnected. Bye bye !");
return $this->redirectToRoute('home');
}
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
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.
I have a simple SecurityBundle following the symfony.com documentation I've created:
UserRepository which implements UserProviderInterface
UserEntity which implements UserInterface and EquatableInterface
SecurityController which has a loginAction
setup routing with login, login_check, logout routes
setup my firewall settings
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('MySecurityBundle:Security:login.html.twig', array(
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
));
}
Everything is OK, but i need add a logger for every action by user, example: USER:username, DO:erase file XXXXX, AT:2013-08-08.15-03-43 the problem is i dont have control in the logout, login action because the firewall catch these and i dont know how to modify
Login event can be catched by security.interactive_login event. Read here.
For logout needs to specify your own success_handler in logout firewall. Read here. The same can be done for a login.
firewalls:
main:
form_login:
failure_handler: mybundle.security.handler.failure
success_handler:mybundle.security.handler.success
logout:
success_handler: mybundle.security.handler.logout