LexikJWT and Schab 2FA - Problem with process auth key - php

I use LexikJWT and Schab2FA Bundle, I configured my security like bellow:
firewalls:
login:
pattern: ^/login
stateless: true
provider: fos_userbundle
json_login:
check_path: /login_check
username_path: _username
password_path: _password
success_handler: App\Application\Module\User\EventHandler\Security\AuthenticationSuccessHandler
failure_handler: App\Application\Module\User\EventHandler\Security\AuthenticationFailureHandler
user_checker: App\Application\Module\User\EventListener\Security\UserChecker
two_factor:
prepare_on_login: true
main:
pattern: ^/
provider: fos_userbundle
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
two_factor:
check_path: 2fa_login_check
auth_code_parameter_name: _auth_code
authentication_required_handler: App\Application\Module\User\EventHandler\Security\TwoFactorAuthenticationRequiredHandler
failure_handler: App\Application\Module\User\EventHandler\Security\TwoFactorAuthenticationFailureHandler
success_handler: App\Application\Module\User\EventHandler\Security\TwoFactorAuthenticationSuccessHandler
scheb_2fa:
# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/6.x/configuration.html
scheb_two_factor:
security_tokens:
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
email:
enabled: true
digits: 6
mailer: App\Application\Module\User\Service\Auth\AuthCodeMailer
lexik_jwt_authentication:
lexik_jwt_authentication:
private_key_path: '%jwt_private_key_path%'
public_key_path: '%jwt_public_key_path%'
pass_phrase: '%jwt_key_pass_phrase%'
token_ttl: '%jwt_token_ttl%'
token_extractors:
cookie:
enabled: true
name: shbee
The problem is, because when I want to confirm my auth code I get a error like:
User is not in a two-factor authentication process.
Because object token is
Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken
Not
use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
I dumped object token class, i tried to change config of 2schab. Probably i must configure something what authorize user by token, but i really don't know what

I had same issue and i fixed it, this is my code
my firewalls
login:
pattern: ^/api/user/login
stateless: false
anonymous: true
two_factor:
prepare_on_login: true
prepare_on_access_denied: true
auth_form_path: 2fa_login # /api/user/login/2fa
check_path: 2fa_login_check # /api/user/login/2fa_check
post_only: true
authentication_required_handler: app.two_factor_authentication_handler_required
success_handler: app.two_factor_authentication_success_handler
failure_handler: app.two_factor_authentication_failure_handler
auth_code_parameter_name: data.authCode
json_login:
username_path: email
check_path: api_login_check ## /api/user/login/login_check
success_handler: app.jwt_authentication_success_handler
#success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
The 2fa_check and api_login_check must match to your pattern.
Next you customize the lexik authentication handler success tho interrupt the login process.
<?php
namespace App\Security;
use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler;
class JWTAuthenticationSuccessHandler extends AuthenticationSuccessHandler
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response
{
if ($token instanceof TwoFactorTokenInterface) {
// Return the response to tell the client two-factor authentication is required.
return new Response('{"login": "success", "success" : false, "message" : "Complete 2FA Verification to proceed", "code" : 410}');
}
return $this->handleAuthenticationSuccess($token->getUser());
}
}
Finaly, customize the two factor authentication handler success to return token on 2fa completed.
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler;
class TwoFactorAuthenticationSuccessHandler extends AuthenticationSuccessHandler
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response
{
// Return the response to tell the client that authentication including two-factor
// authentication is complete now.
return $this->handleAuthenticationSuccess($token->getUser());
}
}
for more information read the documatation of scheb two factor Api integration

Related

Vue Js / Symfony / Api platform Authentication via Session error 401

I watched the latest symfony cast on api platform and security (chapter 2: API Plateform Security) I'm blocking in chapter 5 (Login Success & the Session) of the latter. When I send a POST request with axios from my Vuejs application with the user's credentials, the API does send me the IRI of my user. Until all is well but when I try to send a GET request to my API to display the information of the user in question it returns me a 401 error because yes I have set up a voting system so that there is only the owner of the data that it draws to access it. So my user is not logged in and I am blocking myself there.
Here is my SecurityController login function:
/**
* #Route("/api/login", name="api_login", methods={"POST"})
*/
public function login(IriConverterInterface $iriConverter)
{
if(!$this->isGranted('IS_AUTHENTICATED_FULLY')){
return $this->json([
'error' => 'Invalid login reuqest'
], 400);
}
return $this->json([
'location' => $iriConverter->getIriFromItem($this->getUSer())
]);
}
Here is my security.yaml :
security:
encoders:
App\Entity\User:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: app_user_provider
logout:
path: api_logout
stateless: false
json_login:
check_path: api_login
username_path: email
password_path: password
And here is the annotation my entity User and my user_voter :
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
* #UniqueEntity(fields={"email"}, message="Email existant")
* #ApiResource(
* normalizationContext={"groups"={"user:read"}},
* denormalizationContext={"groups"={"user:write"}},
* collectionOperations={
* "GET",
* "POST",
* },
* itemOperations={
* "GET"= {"security" = "is_granted('USER_VOTER', object)"},
* "PATCH"= {"security" = "is_granted('USER_VOTER', object)"},
* "DELETE"
* },
* )
*/
public function vote(TokenInterface $token, $subject, array $attributes)
{
if(!$subject instanceof User){
return self::ACCESS_ABSTAIN;
}
if(!in_array('USER_VOTER', $attributes)){
return self::ACCESS_ABSTAIN;
}
$user = $token->getUser();
if(!$user instanceof UserInterface){
return self::ACCESS_DENIED;
}
if($subject !== $user){
return self::ACCESS_DENIED;
}
return self::ACCESS_GRANTED;
}
PS: Sorry I started since january on symfony
Use lexik_jwt_authentication for generate jwt token.
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api/v1/
stateless: true
anonymous: true
provider: app_user_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
main:
anonymous: true
json_login:
check_path: /login
username_path: username
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure

authentication_listener not working when using JWT Authentication bundle

I am integrating lexik/jwtautheticationbundle version 1.3 with symfony 2.8 due to old application changes.
I have managed integrate and generate JWT authorization token but I wanted to use cookie and authentication_listener in lexit_jwt and I used but it has no any effect. If I use cookie, token should be saved in cookie but it is saved in session.
Can anyone suggest me why cookie enabled not working ?
Security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
providers:
db_provider:
entity:
class: AppBundle:User
property: username
firewalls:
login:
pattern: ^/api/login
stateless: true
anonymous: true
provider: db_provider
form_login:
check_path: /api/login_check
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
api:
pattern: ^/api
stateless: true
anonymous: true
provider: db_provider
lexik_jwt:
authentication_listener: storefront.listener.jwt_authentication
cookie:
enabled: true
name: IDENTITY
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
services.yml
# Learn more about services, parameters and containers at
# https://symfony.com/doc/current/service_container.html
parameters:
#parameter_name: value
services:
#service_name:
# class: AppBundle\Directory\ClassName
# arguments: ['#another_service_name', 'plain_value', '%parameter_name%']
storefront.listener.jwt_authentication:
class: AppBundle\Listener\AuthenticationListener
arguments:
- "#security.token_storage"
- "#security.authentication.manager"
- []
AuthenicationListener.php
<?php
namespace AppBundle\Listener;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall\JWTListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationListener extends JWTListener
{
public function handle(GetResponseEvent $event): void
{
if (!($requestToken = $this->getRequestToken($event->getRequest()))) {
return;
}
$token = new JWTUserToken();
$token->setRawToken($requestToken);
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($authToken);
return;
} catch (AuthenticationException $failed) {
if ($this->config['throw_exceptions']) {
throw $failed;
}
}
}
}
I thought when cookie is enabled, it will save the token in cookie in the browser, but it meant to just read token from cookie only. So i figured myself. Thank you anyway

Symfony3 and Ajax Authentication

I want members to log in from the frontend and I've defined my authentication handler below and added it as a service which gives me a json response as expected.
<?php
namespace AppBundle\Handler;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
protected $router;
//protected $security;
protected $userManager;
protected $service_container;
public function __construct(RouterInterface $router, $userManager, $service_container)
{
$this->router = $router;
//$this->security = $security;
$this->userManager = $userManager;
$this->service_container = $service_container;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token) {
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
else {
// Create a flash message with the authentication error message
$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);
$url = $this->router->generate('fos_user_security_login');
return new RedirectResponse($url);
}
return new RedirectResponse($this->router->generate('anag_new'));
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
if ($request->isXmlHttpRequest()) {
$result = array('success' => false, 'message' => $exception->getMessage());
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
return new Response();
}
}
However, I am getting same results regardless of whether a user is registered or not. Here is the response
{"success":false,"message":"Bad credentials."}
Here is my security.yml
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: null
logout:
path: /admin/logout
target: /admin/login
anonymous: true
main:
pattern: .*
context: user
form_login:
provider: fos_userbundle
login_path: /login
use_forward: false
check_path: fos_user_security_check
failure_path: null
success_handler: authentication_handler
failure_handler: authentication_handler
logout: true
anonymous: true
routing.yml
fos_user_security_check:
path: /login_check
defaults:
_controller: FOSUserBundle:Security:check
fos_user_security_logout:
path: /logout
defaults:
_controller: FOSUserBundle:Security:logout
The esiest way to implement API authentication for me is to implement the brand new Guard Authentication Interface
http://symfony.com/doc/current/cookbook/security/guard-authentication.html
This simple class allows you to define the process, which instantiate, handles and post-processes authentication.
Enabling the service is as easy as
# app/config/security.yml
security:
# ...
firewalls:
# ...
main:
anonymous: ~
logout: ~
guard:
authenticators:
- app.my_authenticator
# if you want, disable storing the user in the session
# stateless: true
# maybe other things, like form_login, remember_me, etc
# ...
You also need a user provide for this
http://symfony.com/doc/current/cookbook/security/custom_provider.html
Using the Guard you can handle any type of custom authentication (bearer, forms, cookies, GET tokens etc)

Multiple authentication providers in Symfony 2 for a single firewall

I have a Symfony 2.7.6 project with custom Simple Form authentication provider and support for remember me functionality as well as impersonalization feature. Everything works as expected.
However, I want to introduce another authentication provider that will allow requests regardless of session state using two HTTP headers for authentication (e.g. API-Client-Id and API-Client-Token) for third-party applications.
I've created a Simple Pre-Auth authentication provider that validates these header fields and creates authentication token with empty User instance on success.
However, it looks like Symfony is trying to remember those API authentications using session, so I'm getting the following error on the second request: "You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.".
I can set stateless: true flag in my firewall configuration to disable session support, but it will disable it for both auth providers.
SO, how do I preserve existing functionality with my Simple Form authenticator and yet create another layer of authentication to be used for single stateless API requests?
I'm not sure if my approach is conceptually correct. I will gladly accept any suggestions and will provide any relevant information on first request.
Here's my security.yml config:
security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: ~
form_login:
login_path: app.login
check_path: app.session.sign_in
username_parameter: username
password_parameter: password
success_handler: app.security.login_handler
failure_handler: app.security.login_handler
require_previous_session: false
logout:
path: app.session.sign_out
invalidate_session: false
success_handler: app.security.logout_success_handler
# Simple form auth provider
simple_form:
authenticator: app.security.authenticator.out_service
# Token provider
simple_preauth:
authenticator: app.security.authenticator.api_client
remember_me:
name: "%app.session.remember_me.name%"
key: "%secret%"
lifetime: 1209600 # 14 days
path: /
domain: ~
always_remember_me: true
switch_user: { role: ROLE_ADMIN }
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/recover-password, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /, roles: IS_AUTHENTICATED_REMEMBERED }
providers:
main:
entity:
class: App\AppBundle\Model\User
property: id
encoders:
App\AppBundle\Model\User: plaintext
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_ACTIVE]
ROLE_API_CLIENT: ~
ROLE_USER: ~
ROLE_ACTIVE: ~
ApiClientAuthenticator.php:
<?php
namespace App\AppBundle\Security;
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use App\AppBundle\Model\User;
class ApiClientAuthenticator implements SimplePreAuthenticatorInterface
{
/** #var LoggerInterface */
protected $logger;
/** #var array */
protected $clients;
/**
* #param array $clients
*/
public function __construct(array $clients)
{
$this->clients = $clients;
}
public function createToken(Request $request, $providerKey)
{
$clientId = $request->headers->get('Api-Client-Id');
$clientSecret = $request->headers->get('Api-Client-Secret');
if (!$clientId || !$clientSecret) {
return null;
}
return new PreAuthenticatedToken(
'anon.',
[$clientId, $clientSecret],
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
list ($clientId, $clientSecret) = $token->getCredentials();
$foundClient = null;
foreach ($this->clients as $client) {
if ($client['id'] == $clientId) {
if ($client['secret'] == $clientSecret) {
$foundClient = $client;
break;
}
}
}
if (!$foundClient) {
throw new AuthenticationException;
}
$user = new User;
$user->setApiClient(true);
return new PreAuthenticatedToken(
$user,
$foundClient,
$providerKey,
['ROLE_API_CLIENT']
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return ($token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey);
}
}

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