I use ApiPlatform (PHP 8 / Symfony 6) to create a simple API with a JWT authentification.
Authentification work correctly, I can generate a token. When I use PostMan to test a authenticated operation, no problem, I add manually the Bearer header with my token.
Now I would like to use the documentation auto generated to do this.
I have created a JwtDecorator to add my login route and a security schema. Now I can add my token with the "Authorize" green button. But after, when I execute a authenticated operation, the token is not add in the header of the cUrl query. I don't understand why.
security.yaml
security:
enable_authenticator_manager: true
password_hashers:
App\Entity\User: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login
stateless: true
json_login:
check_path: /login
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/
stateless: true
jwt: ~
access_control:
- { path: ^/$, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI
- { path: ^/docs, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI docs
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/, roles: PUBLIC_ACCESS }
JwtDecorator.php
<?php
namespace App\OpenApi;
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\OpenApi;
use ApiPlatform\OpenApi\Model;
final class JwtDecorator implements OpenApiFactoryInterface
{
public function __construct(
private OpenApiFactoryInterface $decorated
) {}
public function __invoke(array $context = []): OpenApi
{
$openApi = ($this->decorated)($context);
$schemas = $openApi->getComponents()->getSchemas();
$schemas['Token'] = new \ArrayObject([
'type' => 'object',
'properties' => [
'token' => [
'type' => 'string',
'readOnly' => true,
],
],
]);
$schemas['Credentials'] = new \ArrayObject([
'type' => 'object',
'properties' => [
'username' => [
'type' => 'string',
'example' => 'test#gmail.com',
],
'password' => [
'type' => 'string',
'example' => '123456',
],
],
]);
$schemas = $openApi->getComponents()->getSecuritySchemes() ?? [];
$schemas['JWT'] = new \ArrayObject([
'type' => 'http',
'scheme' => 'bearer',
'bearerFormat' => 'JWT',
]);
$pathItem = new Model\PathItem(
ref: 'JWT Token',
post: new Model\Operation(
operationId: 'postCredentialsItem',
tags: ['Token'],
responses: [
'200' => [
'description' => 'Get JWT token',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/Token',
],
],
],
],
],
summary: 'Get JWT token to login.',
requestBody: new Model\RequestBody(
description: 'Generate new JWT Token',
content: new \ArrayObject([
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/Credentials',
],
],
]),
),
security: [],
),
);
$openApi->getPaths()->addPath('/login', $pathItem);
return $openApi;
}
}
NotificationCategory.php
<?php
namespace App\Entity;
use ...
#[ORM\Entity(repositoryClass: NotificationCategoryRepository::class)]
#[ApiResource(
openapiContext: ['security' => [['Bearer Authentication' => []]]],
security: "is_granted('ROLE_USER')"
)]
class NotificationCategory
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Groups(['read:Notification:get'])]
private ?string $name = null;
#[ORM\OneToMany(mappedBy: 'category', targetEntity: Notification::class)]
private Collection $notifications;
...
}
I finally found. I was not using the correct schema name in my entity's openapiContext. In JwtDecorator file, I name the schema JWT, and in NotificationCategory entity : Bearer Authentication.
So I replaced :
#[ApiResource(
openapiContext: ['security' => [['Bearer Authentication' => []]]],
security: "is_granted('ROLE_USER')"
)]
By :
#[ApiResource(
openapiContext: ['security' => [['JWT' => []]]],
security: "is_granted('ROLE_USER')"
)]
It work.
I apologize for my English.
Some time ago I encountered a problem when developing my project on the Symfony. The problem arose in SonataAdminBundle.
The error looks like this
My Admin class
<?php
namespace Flatbel\FlatBundle\Admin;
use Flatbel\FlatBundle\Entity\User;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
class AdminAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Основная информация', array('class' => 'col-md-8'))
->remove('userid')
->add('flattype', 'choice', array(
'choices' => array(
'VIP' => 'VIP',
'Стандарт' => 'Стандарт',
'Бюджет' => 'Бюджет'
),
'choices_as_values' => true, 'label' => 'Тип квартиры', 'placeholder'=>'Выбрать...'
))
->add('numberofbeds','choice', array(
'choices' => array(
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
'6+' => '7',
),
'choices_as_values' => true, 'label'=>'Количество спальных мест', 'placeholder'=>'Выбрать...'
))
->add('rooms','choice', array(
'choices' => array(
'1' => '1',
'2' => '2',
'3' => '3',
'4+' => '4',
),
'choices_as_values' => true, 'label'=>'Число комнат', 'placeholder'=>'Выбрать...'
))
->add('streettype', 'choice', array(
'choices' => array(
'Проспект' => 'Проспект',
'Улица'=>'Улица',
'Переулок'=>'Переулок'
),
'choices_as_values' => true, 'label'=>'Тип', 'placeholder'=>'Выбрать...'
))
->add('street', 'text', array('label'=>'Название'))
->add('home',null,array('label'=>'Номер дома'))
->add('priceday',null,array('label'=>'Цена за день'))
->add('pricehour',null,array('label'=>'Цена за час'))
->add('pricenight',null,array('label'=>'Цена за ночь'))
->add('floorhome',null,array('label'=>'Число этажей в дома'))
->add('floor',null,array('label'=>'Этаж'))
->add('metro','choice', array(
'choices' => array(
'Каменная горка'=>'Каменная горка',
'Кунцевщина'=>'Кунцевщина',
),
'choices_as_values' => true, 'label'=>'Ближайшее метро', 'placeholder'=>'Выбрать...'
))
->add('telnumber',null,array('label'=>'Номер телефона'))
->add('about',null,array('label'=>'Описание'))
->end()
->with('Дополнительная информация',array('class'=>'col-md-4'))
->add('tv',null,array('label'=>'Телевизор'))
->add('wifi',null,array('label'=>'Wi-Fi'))
->add('parking',null,array('label'=>'Стоянка'))
->add('microwave',null,array('label'=>'Микроволновка'))
->add('washer',null,array('label'=>'Стиральная Машина'))
->add('bath',null,array('label'=>'Ванна'))
->add('shower',null,array('label'=>'Душ'))
->add('fridge',null,array('label'=>'Холодильник'))
->add('dishes',null,array('label'=>'Посуда'))
->add('linens',null,array('label'=>'Постельное бельё'))
->remove('payornot')
->remove('description')
->end()
->with('Фотографии',array('class'=>'col-md-8'))
->add('mainphoto', 'sonata_media_type', array(
'provider' => 'sonata.media.provider.image',
'context' => 'flatphotos',
))
->add('photo1','sonata_media_type', array(
'provider' => 'sonata.media.provider.image',
'context' => 'flatphotos',
))
->add('photo2','sonata_media_type', array(
'provider' => 'sonata.media.provider.image',
'context' => 'flatphotos',
))
->add('photo3','sonata_media_type', array(
'provider' => 'sonata.media.provider.image',
'context' => 'flatphotos',
))
->end()
;
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')
->addIdentifier('userid')
->addIdentifier('payornot')
->addIdentifier('flattype')
->addIdentifier('numberofbeds')
->addIdentifier('rooms')
->addIdentifier('street')
->addIdentifier('streettype')
->addIdentifier('home')
->addIdentifier('priceday')
->addIdentifier('pricehour')
->addIdentifier('pricenight')
->addIdentifier('floorhome')
->addIdentifier('floor')
->addIdentifier('tv')
->addIdentifier('wifi')
->addIdentifier('parking')
->addIdentifier('microwave')
->addIdentifier('washer')
->addIdentifier('bath')
->addIdentifier('shower')
->addIdentifier('fridge')
->addIdentifier('dishes')
->addIdentifier('linens')
;
}
public function toString($object)
{
return $object instanceof User
? $object->getUsername()
: 'Flat'; // shown in the breadcrumb on the create view
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('id')
->add('userid')
->add('payornot')
->add('flattype')
->add('numberofbeds')
->add('rooms')
->add('street')
->add('streettype')
->add('home')
->add('priceday')
->add('pricehour')
->add('pricenight')
->add('floorhome')
->add('floor')
->add('tv')
->add('wifi')
->add('parking')
->add('microwave')
->add('washer')
->add('bath')
->add('shower')
->add('fridge')
->add('dishes')
->add('linens')
;
}
}
My config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
- { resource: "#FlatbelFlatBundle/Resources/config/config.yml"}
parameters:
locale: ru
photo_directory: 'uploads/photos/'
framework:
translator: { fallbacks: ["%locale%"] }
default_locale: ru
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
handler_id: ~
fragments: ~
http_method_override: true
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
form:
resources:
- 'FlatbelFlatBundle:Form:media_widgets.html.twig'
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
options:
1002: 'SET NAMES UTF8'
types:
json: Sonata\Doctrine\Types\JsonType
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
mappings:
ApplicationSonataMediaBundle: ~
SonataMediaBundle: ~
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
#FOSUserBundle Configuration
fos_user:
db_driver: orm # other valid values are 'mongodb' and 'couchdb'
firewall_name: main
user_class: Flatbel\FlatBundle\Entity\User
use_listener: true
use_flash_notifications: true
use_authentication_listener: true
use_username_form_type: true
from_email:
address: "%mailer_user%"
sender_name: "%mailer_user%"
profile:
form:
type: fos_user_profile # or 'fos_user_profile' on Symfony < 2.8
name: fos_user_profile_form
validation_groups: [Profile, Default]
change_password:
form:
type: fos_user_change_password # or 'fos_user_change_password' on Symfony < 2.8
name: fos_user_change_password_form
validation_groups: [ChangePassword, Default]
registration:
confirmation:
from_email: # Use this node only if you don't want the global email address for the confirmation email
address: registration#flatbel.by
sender_name: Registration
enabled: true # change to true for required email confirmation
template: '#FOSUser/Registration/email.txt.twig'
form:
type: fos_user_registration # or 'fos_user_registration' on Symfony < 2.8
name: fos_user_registration_form
validation_groups: [Registration, Default]
resetting:
token_ttl: 86400
email:
from_email: # Use this node only if you don't want the global email address for the resetting email
address: resetting#flatbel.by
sender_name: Resetting
template: email/password_resetting.email.twig
form:
type: fos_user_resetting # or 'fos_user_resetting' on Symfony < 2.8
name: fos_user_resetting_form
validation_groups: [ResetPassword, Default]
service:
mailer: fos_user.mailer.default
email_canonicalizer: fos_user.util.canonicalizer.default
username_canonicalizer: fos_user.util.canonicalizer.default
token_generator: fos_user.util.token_generator.default
user_manager: fos_user.user_manager.default
#SonataAdminBundle
sonata_block:
default_contexts: [cms]
blocks:
# enable the SonataAdminBundle block
sonata.admin.block.admin_list:
contexts: [admin]
# ...
sonata_admin:
security:
handler: sonata.admin.security.handler.role
assetic:
debug: "%kernel.debug%"
use_controller: false
bundles: [FlatbelFlatBundle]
filters:
cssrewrite: ~
assets:
bootstrap_js:
inputs:
- "%kernel.root_dir%/../vendor/twitter/bootstrap/dist/js/bootstrap.js"
bootstrap_css:
inputs:
- "%kernel.root_dir%/../vendor/twitter/bootstrap/dist/css/bootstrap.css"
- "%kernel.root_dir%/../vendor/twitter/bootstrap/dist/css/bootstrap-theme.css"
filters: [cssrewrite]
bootstrap_glyphicons_ttf:
inputs:
- "%kernel.root_dir%/../vendor/twitter/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf"
output: "fonts/glyphicons-halflings-regular.ttf"
bootstrap_glyphicons_eot:
inputs:
- "%kernel.root_dir%/../vendor/twitter/bootstrap/dist/fonts/glyphicons-halflings-regular.eot"
output: "fonts/glyphicons-halflings-regular.eot"
bootstrap_glyphicons_svg:
inputs:
- "%kernel.root_dir%/../vendor/twitter/bootstrap/dist/fonts/glyphicons-halflings-regular.svg"
output: "fonts/glyphicons-halflings-regular.svg"
bootstrap_glyphicons_woff:
inputs:
- "%kernel.root_dir%/../vendor/twitter/bootstrap/dist/fonts/glyphicons-halflings-regular.woff"
output: "fonts/glyphicons-halflings-regular.woff"
jquery:
inputs:
- "%kernel.root_dir%/../vendor/components/jquery/jquery.js"
sonata_media:
db_driver: doctrine_orm # or doctrine_mongodb, doctrine_phpcr it is mandatory to choose one here
default_context: default # you need to set a context
contexts:
default: # the default context is mandatory
providers:
- sonata.media.provider.dailymotion
- sonata.media.provider.youtube
- sonata.media.provider.image
- sonata.media.provider.file
- sonata.media.provider.vimeo
formats:
small: { width: 100 , quality: 70}
big: { width: 500 , quality: 70}
flatphotos:
providers:
- sonata.media.provider.image
formats:
big: { width: 500 , quality: 70}
cdn:
server:
path: /uploads/media # http://media.sonata-project.org/
filesystem:
local:
directory: "%kernel.root_dir%/../web/uploads/media"
create: false
Please, help me. I've been trying to find my own mistake for the second week, I've been looking for similar problems on the Internet - and I do not find anything.
EDIT
My services.yml
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/service_container.html
parameters:
# parameter_name: value
services:
FlatbelFlatFileUploader:
class: Flatbel\FlatBundle\Service\FileUploader
arguments:
$targetDir: '%photo_directory%'
admin.user:
class: Flatbel\FlatBundle\Admin\UserAdmin
arguments: [~, Flatbel\FlatBundle\Entity\User,~]
tags:
- { name: sonata.admin, manager_type: orm, label: User }
admin.flat:
class: Flatbel\FlatBundle\Admin\FlatAdmin
arguments: [~, Flatbel\FlatBundle\Entity\Flat,~]
tags:
- { name: sonata.admin, manager_type: orm, label: Flat }
admin.admin:
class: Flatbel\FlatBundle\Admin\AdminAdmin
arguments: [~, Flatbel\FlatBundle\Entity\Flat,~]
tags:
- { name: sonata.admin, manager_type: orm, label: Admin}
Your admin class extends AbstractAdmin and has no any added fields in it's form mapper. So, you don't need this: ->remove('userid'). I tried to type in my admin class the same thing and got "Notice: Undefined index: translation_domain" too.
I try to create a connection system with form.
But I still get the message: "Bad credentials."
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
Tp\Bundle\AppBundle\Entity\User: plaintext
providers:
in_memory:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
tp:
entity: { class: TpAppBundle:User, property: email }
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login$
security: false
tp:
pattern: ^/
provider: tp
#anonymous: ~
form_login:
login_path: login
check_path: login_check
default_target_path: user_index
always_use_default_target_path: true
logout:
path: logout
target: login
<?php
namespace Tp\Bundle\AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;
class SecurityController extends Controller
{
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// get the login error if there is one
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('TpAppBundle:Security:login.html.twig', array(
// last username entered by the user
'email' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
));
}
}
class User implements UserInterface {
...
In my date base,
User
email:test#test.com
password: test
But unable to connect.
I still have the same message: Bad credentials.
Have you any idea?
Do you need more?
Thank you very much
I want to programmatically log in an user using the FOSRestBundle (I am not using FOSUserBundle). It seems to work, but when I logged in successfully and try to access an secured endpoint, Symfony throws an AccessDeniedException.
This is my security.yml:
security:
providers:
main:
entity:
class: DepartureMonitor\RestBundle\Entity\User
property: email
role_hierarchy:
ROLE_EDITOR: [ ROLE_USER ]
ROLE_VU_ADMIN: [ ROLE_EDITOR, ROLE_USER ]
ROLE_ADMIN: [ ROLE_VU_ADMIN ]
encoders:
DepartureMonitor\RestBundle\Entity\User:
algorithm: bcrypt
cost: 13
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
rest:
pattern: ^/api
provider: main
anonymous: true
form_login:
login_path: login
check_path: login_check
logout: ~
access_control:
- { path: ^/api/users/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/users/password, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: ROLE_USER }
In my UsersController I have the following action:
/**
* #Post("/users/login")
*/
public function loginAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('DepartureMonitorRestBundle:User')->findOneBy(array('email' => $request->request->get('username')));
if ($user instanceof User) {
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$pw = $encoder->encodePassword($request->request->get('password'), $user->getSalt());
if($pw === $user->getPassword()) {
$token = new UsernamePasswordToken($user, $pw, "main", $user->getRoles());
$this->get('security.context')->setToken($token);
$event = new \Symfony\Component\Security\Http\Event\InteractiveLoginEvent($request, $token);
$this->get('event_dispatcher')->dispatch('security.interactive_login', $event);
if(!($this->getUser() instanceof User)) {
return $this->view(array('code' => HttpStatusCode::NOT_FOUND, 'message' => $this->get('translator')->trans('error.messages.bad_credentials')), HttpStatusCode::NOT_FOUND);
}
$view = $this->view($user, HttpStatusCode::CREATED);
$view->setSerializationContext(SerializationContext::create()->setGroups(array('login')));
return $this->handleView($view);
}
}
return $this->view(array('code' => HttpStatusCode::NOT_FOUND, 'message' => $this->get('translator')->trans('error.messages.bad_credentials')), HttpStatusCode::NOT_FOUND);
}
I don't know what the problem here is.
Any help is very appreciated.
I am new in symfony2 and I am trying to learn login page in symfony2 but system gives me this error
The security context contains no authentication token. One possible reason may be that there is no firewall configured for this URL.
My security.yml code is:
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
in_memory:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }
admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/demo/secured/login$
security: false
emp:
pattern: ^/emp
form_login:
check_path: acme_radixemp_logincheck
login_path: acme_radixemp_login
logout:
path: acme_radixemp_logout
target: demo
#anonymous: ~
#http_basic:
# realm: "Secured Demo Area"
access_control:
- { path: ^/emp, roles: ROLE_ADMIN }
And I am trying to run /emp from url
And my login function in controller is :
public function loginAction(Request $request)
{
$login = new user();
$form = $this->createFormBuilder($login)
->add('Username','text',array('attr' => array('size' => '50',"value"=>"",'class'=>'form-control','placeholder'=>"Please Enter Your Username")))
->add('Password', 'password',array('attr' => array('size' => '50',"value"=>"",'class'=>'form-control','placeholder'=>"Enter Your Password")))
->add('login', 'submit',array('attr' => array('class '=>"btn btn-success btn-lg","style"=>"margin-top:15px")))
->getForm()
;
$form->handleRequest($request);
$session = $this->getRequest()->getSession();
$url = $this->generateUrl("emp");
$loginurl = $this->generateUrl("acme_radixemp_login",array("login"=>"fail"));
$postData = $request->request->get('form');
if(!empty($postData)) {
$repository = $this->getDoctrine()->getRepository('AcmeRadixempBundle:user');
$user_name = $repository->findOneBy(array('username' => $postData['Username'],'password'=>$postData['Password']));
if($user_name=="") {
return $this->redirect($loginurl);
} else {
//$this->get('security.context')->isGranted('ROLE_ADMIN');
if(TRUE ===$this->get('security.context')->isGranted('ROLE_ADMIN') )
{
$session->set('username', $postData['Username']);
return $this->redirect($url);
}
}
}
}
return $this->render('AcmeRadixempBundle:Default:login.html.twig',array('login'=> $form->createView()));
}
What did I forgot to do ?
Your check_path and login_path are not standard. If the actions for your settings are not implemented and the correct paths are not registered, your login functionality won't work.
I'd suggest you follow the example in the Symfony book:
Using a Traditional Login Form
I followed it step-by-step and it worked immediately. For simple logins you won't be needing to create the loginAction or logoutAction at all, the Symfony will do everything for you. The only thing you need to create (and you can copy-paste it from the link I've sent you) is the login form. You can find all other more complex information in the book and the cookbook, both available freely on the Symfony website.
Hope this helps.