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
Related
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
I am trying to get a json_api based login system working in my project.
My problem is that when i click my login button, I get an unauthorized error message that there are missing credentials. When i dump the user in the response it shows that it is null.
Here is my controller function:
#[Route('/entry/login', name: 'api_login')]
public function login(#[CurrentUser] ?User $user): Response
{
if (null === $user) {
return $this->json([
'message' => 'missing credentials',
'user' => $user
], Response::HTTP_UNAUTHORIZED);
}
return $this->json(['user' => $user->getUserIdentifier()]);
}
My security.yaml:
security:
enable_authenticator_manager: true
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
users:
entity:
class: 'App\Entity\User'
property: 'email'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
json_login:
check_path: api_login
and my front-end post request:
private async submit(): Promise<void> {
if ((this as any).$refs.form.validate() && this.valid) {
const user = {
username: this.email,
password: this.password,
}
const response = await axios.post('/entry/login', user);
}
}
I have been referencing this page, and I think my code should work like this but it seems that i am missing something.
Any help would be appreciated.
If you need more code or anything you may ask.
Please try to set following fields explicitly (username_path & password_path):
firewall:
main:
lazy: true
json_login:
check_path: api_login
username_path: email
password_path: password
Because you are using entity property: 'email'. Also change username to email in your frontend request.
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
I'm implementing Lexik JWT library with Sf 4.1.
In my case I have to create a JWT Token when needed for several applications through custom authenticator.
I have followed the lexik documentation, however I am facing an issue for couple of hours for signing my token.
The only thing different than casual case : I use doctrine-odm insteand of doctrine-orm for using MongoDb.
Here the files :
security.yaml :
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
anonymous: ~
logout:
path: /logout
target: /login
remember_me:
secret: '%env(APP_SECRET)%'
guard:
authenticators:
- App\Security\GuardAuthenticator\LoginFormAuthenticator
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
lexik_jwt_authentication.yaml :
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(resolve:JWT_PASSPHRASE)%'
token_ttl: 3600
LoginFormAuthenticator.php (onAuthenticationSuccess method) :
/**
* #param Request $request
* #param TokenInterface $token
* #param string $providerKey
*
* #return null|JsonResponse
*/
public function onAuthenticationSuccess(
Request $request,
TokenInterface $token,
$providerKey
): ?JsonResponse {
/** #var User $user */
$user = $token->getUser();
$apiToken = $this->jwtTokenManager->create($user);
$user->setApiToken($apiToken);
$this->documentManager->persist($user);
$this->documentManager->flush();
return new JsonResponse(['Authorization' => $apiToken]);
}
private.pem :
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,F05739F4D47EE90DADA678BA60000AE4
<sensitive data>
-----END RSA PRIVATE KEY-----
I tried to inspect parameters passed to create or sign method in vendor :
The "key" parameter passed is the path string to the file, and it is not working, getting "
Unable to create a signed JWT from the given configuration." error
Do you have any piece of advice to help me please ?
use this method to create jwt token
public function getTokenUser(JWTTokenManagerInterface $JWTManager,ManagerRegistry $mr,UserPasswordHasherInterface $hasher)
{
$em = $mr->getManager();
$user = $em->getRepository(User::class)->findOneBy(['email' => 'user']);
if($hasher->isPasswordValid($user, 'user')){
$token = $JWTManager->create($user);
return new JsonResponse(['token' => $token]);
}
return new JsonResponse(['error' => 'Invalid credentials'], Response::HTTP_UNAUTHORIZED);
// ...
}
I'm developing a RESTful web service in Symfony2 with FOSRest and FOSOauthServer bundles (... and many others). My problem is that with an access token of other user, the api gives response instead of a 403 status code. For example:
I have two users stored on database
userA with tokenA
userB with tokenB
Example Request
http://example.com/api/v1/userA/products?access_token=tokenB
Current Response
{
products: {
0: { ... }
1: { ... }
}
}
But I'm requesting products of user A with an access token of user B. How could I check if access token provided is of the products' owner??
My security.yml file:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
MY_ROLE:
# ...
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
SONATA:
- ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: null
logout:
path: /admin/logout
anonymous: true
# FOSOAuthBundle and FOSRestBundle
oauth_token:
pattern: ^/oauth/v2/token
security: false
# oauth_authorize: commented because there are not oauth login form on this app
# pattern: ^/oauth/v2/auth
# Add your favorite authentication process here
api:
pattern: ^/api
fos_oauth: true
stateless: true
anonymous: false
# This firewall is used to handle the public login area
# This part is handled by the FOS User Bundle
main:
# ...
access_control:
# ...
# API (FOSRestBundle and FOSOAuthBundle)
- { path: ^/api, roles: [IS_AUTHENTICATED_FULLY] }
My routing.yml on ApiBundle
# API Endpoints
app_api_user_get_products:
pattern: /{username}/products
defaults: { _controller: ApiBundle:User:getProducts, _format: json }
methods: GET
My UserController.php
<?php
namespace App\ApiBundle\Controller;
Use App\MainBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ... more use statments
class UserController extends ApiController {
/**
* List user's products.
*
* #ApiDoc(
* resource = true,
* description="This method must have the access_token parameter. The parameters limit and offset are optional.",
* filters={
* {"name"="access_token", "dataType"="string", "required"="true"},
* {"name"="offset", "dataType"="integer", "default"="0", "required"="false"},
* {"name"="limit", "dataType"="integer", "default"="250", "required"="false"}
* },
* )
*
* #Annotations\QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing products.")
* #Annotations\QueryParam(name="limit", requirements="\d+", default="500", description="How many products to return.")
*
* #Annotations\View()
*
* #param User $user the request object
* #param ParamFetcherInterface $paramFetcher param fetcher service
*
* #return array
*/
public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) {
// $offset = $paramFetcher->get('offset');
// $offset = null == $offset ? 0 : $offset;
// $limit = $paramFetcher->get('limit');
try {
// estructure and exclude fields strategy http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies
$data = array('products' => array());
foreach ($user->getCatalog() as $p) {
if ($p->getAvailable() == true) {
$product = $p->getProduct();
$data['products'][] = array(
'id' => $product->getId(),
'reference' => $product->getReference(),
'brand' => $product->getBrand(),
'description' => $product->getDescription(),
// ...
);
}
}
} catch (\Exception $e) {
throw new HttpException(Codes::HTTP_INTERNAL_SERVER_ERROR, $e->getTraceAsString());
}
// New view
$view = new View($data);
$view->setFormat('json');
return $this->handleView($view);
}
}
Thank you very much for the help!
I've found the solution. It's easy, just I've added the following code in my rest controller and the configuration parameters on app/config.yml
UserController.php
...
public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) {
// Check if the access_token belongs to the user making the request
$requestingUser = $this->get('security.context')->getToken()->getUser();
if (!$requestingUser || $requestingUser !== $user) {
throw new AccessDeniedHttpException();
}
...
~/app/config.yml
# FOSRestBundle
fos_rest:
routing_loader:
default_format: json
param_fetcher_listener: true
view:
view_response_listener: force
access_denied_listener: # I've added this
# all requests using the 'json' format will return a 403 on an access denied violation
json: true
You can also make it simpler using #Security annotation in Symfony >= 2.4 . In your case it'll look like
/**
* #Security("user.getId() == userWithProducts.getId()")
*/
and the action header:
...
public function getProductsAction(User $userWithProducts, ParamFetcherInterface $paramFetcher, Request $request) {
...