I am implementing a multi tenant application where the customer requires me to follow a specific data model. The customer required two seperate login systems that I am implementing using the guard system.
The first one is used to login customers. The difficulty here is that it is required to store the users passwords in the customer's database. To accomplish this I have implementation the guard system to first looks up the username in the 'login' database. From that same database the (encrypted) connection parameters for the users application database are retrieved. Using these parameters the users application database connection is created dynamically ('app') and the user is logged in using the information in this application database. On subsequent page requests the users application database is dynamically connected using a service on triggers on each pageload. So far so good.
The second Guard system is used to login to the back-end for management tasks. This login system is actually much simpler and uses a single database 'admin_login' to verify the users credentials. (So username, password and all other user properties are in one single database). The problem occurs after successfully logging in on the 2nd Guard implementation. The forward to the back-end home page triggers an error and I am unable to find to cause of this.
The error message is:
The class 'AppBundle\Entity\Login\Admin\AdminUser' was not found in the chain configured namespaces AppBundle\Entity\App
The error triggers before any code from the corresponding controller is hit. When I set the 'intercept_redirects' configuration item to true I can see that the administrative user is fully authorized before the redirect is taking place.
My doctrine configuration in config.yml
# Doctrine Configuration
doctrine:
dbal:
default_connection: app # specify the connexion used by default
connections:
login:
driver: pdo_mysql
host: '%database_login_host%'
port: '%database_login_port%'
dbname: '%database_login_name%'
user: '%database_login_user%'
password: '%database_login_password%'
charset: utf8mb4
admin_login:
driver: pdo_mysql
host: '%database_admin_host%'
port: '%database_admin_port%'
dbname: '%database_admin_name%'
user: '%database_admin_user%'
password: '%database_admin_password%'
charset: utf8mb4
app:
driver: pdo_mysql
host: '%database_app_host%'
port: '%database_app_port%'
dbname: '%database_app_name%'
user: '%database_app_user%'
password: '%database_app_password%'
charset: utf8mb4
orm:
auto_generate_proxy_classes: "%kernel.debug%"
default_entity_manager: app # specify the EM used by default (when using console commands f.e)
entity_managers:
login:
connection: login
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: false
mappings:
AppBundle :
type: annotation
dir: Entity/Login/Customer
prefix: AppBundle\Entity\Login\Customer
alias: Login
app:
connection: app
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: false
mappings:
AppBundle :
type: annotation
dir: Entity/App
prefix: AppBundle\Entity\App
alias: App
filters:
customer_flt:
class: AppBundle\Security\CustomerFilter
admin:
connection: admin_login
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: false
mappings:
AppBundle :
type: annotation
dir: Entity/Login/Admin
prefix: AppBundle\Entity\Login\Admin
alias: Admin
My security.yml
security:
encoders:
AppBundle\Entity\App\User:
algorithm: bcrypt
cost: 12
AppBundle\Entity\Login\Admin\AdminUser:
algorithm: bcrypt
cost: 12
role_hierarchy:
ROLE_SUPER_ADMIN: ROLE_ADMIN
ROLE_ADMIN: ROLE_USER
# http://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
app:
entity:
class: AppBundle\Entity\App\User
property: username
admin:
entity:
class: AppBundle\Entity\Login\Admin\AdminUser
property: login
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/app
anonymous: ~
guard:
authenticators:
- app.security.login_form_authenticator
logout:
path: /app/logout
target: /
provider: app
admin:
pattern: ^/admin
anonymous: ~
guard:
authenticators:
- backend.security.login_form_authenticator
logout:
path: /admin/logout
target: /
provider: admin
access_control:
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_SUPER_ADMIN }
- { path: ^/app/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/app, roles: ROLE_USER }
What am I missing here?
Issue solved! Just an update so that everyone can find the answer when googling for it.
The issue was caused by a missing manager_name property in the providers section of the security.yml file. This property provides a mapping from a provider to a specific entity_manager. Without these Symfony takes the first (or default) entity manager and that fails (obviously) in my scenario.
providers:
app:
entity:
class: AppBundle\Entity\App\User
property: username
manager_name: app
admin:
entity:
class: AppBundle\Entity\Login\Admin\AdminUser
property: login
manager_name: admin
Sparse documentation of this can be found here: http://symfony.com/doc/current/security/entity_provider.html
Related
I have two symfony installations. One is a website and serves to store the users, sessions, account info, etc. The latter is an customer specific application install, with their own distinct DB and tables.
Authentication works fine on the main site. The idea being I could authenticate SSO and redirect the user back to their app instance.
I have managed to replicate my auth configuration in the application instances (mostly) how the application instances have slightly different doctrine config:
# config/packages/doctrine.yaml
doctrine:
dbal:
default_connection: default
connections:
default:
# configure these for your database server
url: '%env(resolve:DATABASE_URL)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
account:
# Sessions and users are stored here
url: '%env(resolve:DATABASE_URL_SITE)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
orm:
default_entity_manager: default
entity_managers:
default:
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
connection: default
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/Core'
prefix: 'App\Entity'
alias: App
account:
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
connection: account
mappings:
Sso:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/Shared'
prefix: 'App\Entity\Shared'
alias: Sso
This works - kinda - until Symfony tries to authenticate. The user entity is being retreived from the application DB, which does not have the Users table.
I thought the above ORM configuration made this obvious? All other scripts (schema:create, etc) seem to work...what am I missing?!?
EDIT security.yaml
security:
#enable_authenticator_manager: true
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\Shared\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: app_user_provider
#custom_authenticators:
# - App\Security\SessionAuthenticator
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
#- { path: ^/admin, roles: ROLE_USER }
# - { path: ^/profile, roles: ROLE_USER }
I want two authentications methods in my application.
One for the entity User, and other (admin) with a plaintext.
Very simple.
Thus, when I configure security.yaml, I specify the providers:
security:
providers:
user:
entity:
class: App\Entity\User
property: username
in_memory:
memory:
users:
admin:
password: admin
roles: 'ROLE_ADMIN'
encoders:
App\Entity\User: bcrypt
Symfony\Component\Security\Core\User\User: plaintext
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
provider: in_memory
pattern: ^/admin/
guard:
provider: in_memory
form_login:
login_path: admin_login
check_path: admin_login
logout:
path: /admin/logout
target: /
default:
provider: user
anonymous: ~
guard:
provider: user
form_login:
login_path: login
check_path: login
default_target_path: login_redirect
use_referer: true
logout:
path: /logout
target: /
access_control:
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/dashboard, roles: ROLE_USER }
And return the error:
In GuardAuthenticationFactory.php line 121:
Because you have multiple guard configurators, you need to set the "guard.e
ntry_point" key to one of your configurators ()
Then, if I have to set the guard.entry_point, I need do something like this:
admin:
entry_point: app.form_admin_authenticator
main:
entry_point: app.form_user_authenticator
And therefore, if I undestard, I need to configure a Authentication Listener like this: https://symfony.com/doc/current/components/security/authentication.html
(btw, this particular help page is very ambiguous and incomplete)
Is it necessary? It seems too complex for my purpose
I ran into this particular error. My situation might be a little different, but I had a similar need to authenticate using different authentication strategies depending on the entry point to the application.
One thing your config doesn't include is a reference to any Guard Authenticator objects. See this documentation for an intro to what role those objects play, and how to define them. Symfony's Security package is pretty complicated, and I found that using Guard Authenticators made the process a lot simpler for my use case.
Here is an example of a security.yaml config referencing two different authenticators. The entry_point configuration tells Symfony which one to try first, because in my particular case, Symfony otherwise wouldn't know which authentication strategy to apply first to an incoming request.
security:
providers:
user:
id: App\My\UserProviderClass
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
logout:
path: app_logout
guard:
entry_point: App\My\MainAuthenticator
authenticators:
- App\My\MainAuthenticator
- App\My\OtherAuthenticator
Custom Guard Authenticators contain a method called supports. This method takes the incoming request as its only argument, and returns true or false based on whether the given authenticator should be applied to the incoming request. A common practice might be to check the request's Symfony route name (as defined by the controller) or perhaps something like the full URI for the request. For example:
/**
* Does the authenticator support the given Request?
*
* If this returns false, the authenticator will be skipped.
*
* #param Request $request
*
* #return bool
*/
public function supports(Request $request): bool
{
$matchesMyRoute = 'some_route_name' ===
$request->attributes->get('_route');
$matchesMyUri = '/path/to/secured/resource' ===
$request->getUri();
return $matchesMyRoute || $matchesMyUri;
}
You can imagine that if multiple Guard Authenticators exist in the same application, it's likely the case that one would only want them to apply to a request of a certain type, whether the differentiation is based on the kind of auth applied (eg. a header with an API key vs. a stateful session cookie), whether the difference is more about the specific route being hit, or perhaps a combination of factors.
In this case, telling Symfony which Guard Authenticator to try first may be important for your security strategy, or may have performance ramifications. For example, if you had two authenticators, and one had to hit the database to verify a stateful session, but another could verify the request's authentication statelessly, eg. by verifying a JWT's signature, you'd probably want to make the JWT authenticator run first, because it might not need to make a round trip to the database to authenticate the request.
See this article for a deeper explanation: https://symfonycasts.com/screencast/symfony-security/entry-point
I am pretty new to symfony and try to handle that framework.
Use symfony 2.8 with sonata admin 2.3 and user bundle 2.2.5
Got some interesting situation with ACL roles adding.
I have 2 roles in my project(user and admin) and needed to add one more(manager), but got stuck on situation with total ignore of any of that new role.
I am allowed to log in with the no problems but no dashboard is loaded to the admin_pool.
It's a part of my security.yml with allowed roles.
acl:
connection: default
providers:
fos_userbundle:
id: fos_user.user_manager
role_hierarchy:
ROLE_USER: [VIEW, LIST]
ROLE_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN]
ROLE_DIVISION_MANAGER: [OPERATOR, EXPORT, MASTER, OWNER]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
and a part of my config.yml
where roles should be influence dashboard view.
dashboard:
blocks:
-
position: left
type: sonata.admin.block.admin_list
settings:
groups: [sonata_divison]
groups:
grower.management:
label: "Grower Management"
roles: [ADMIN]
sonata_user:
label: "Users"
roles: [ADMIN]
sonata_divison:
label: "Division Management"
roles: [ROLE_DIVISION_MANAGER, DIVISION_MANAGER]
items:
- admin.grower
- sonata.user.admin.user
security:
handler: sonata.admin.security.handler.acl
# acl security information
information:
OPERATOR: OPERATOR
EXPORT: EXPORT
LIST: LIST
VIEW: VIEW
USER: [VIEW, LIST]
DIVISION_MANAGER: [OPERATOR, EXPORT]
ADMIN: [MASTER, OWNER]
Are there any thoughts why dashboard is blank for division manager?
Morning folks,
mainly i want to secure all call against a url that starts with /api/internal.
All endpoints that start with this path are only for internal calls, e.g. in a ajax-search box. So right me when i am wrong but i thought it would be a good idea to secure this via host definition in access_roles
I tried it with the following security.yml
security:
role_hierarchy:
ROLE_myproject_USER: ROLE_USER
ROLE_TEAMMANAGER: ROLE_USER
ROLE_ADMIN: [ROLE_TEAMMANAGER]
providers:
dashboard_users:
ldap:
service: myproject.ldap
# my ldap config
custom_user_provider:
id: myproject.factory.scale_user
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_internal:
pattern: ^/api/internal
security: true
host: myproject.dev
api_doc:
pattern: ^/api/doc
security: false
api_login:
pattern: ^/api/login
stateless: true
anonymous: true
api:
pattern: ^/api
stateless: true
provider: custom_user_provider
guard:
authenticators:
- myproject.api_login_authenticator
main:
anonymous: ~
form_login_ldap:
login_path: myproject_login
check_path: myproject_login
service: myproject.ldap
dn_string: 'Verbund\{username}'
logout:
path: myproject_logout
target: /
access_control:
- { path: ^/api/internal, host: myproject.dev }
But i get this error:
InvalidConfigurationException in SecurityExtension.php line 481:
No authentication listener registered for firewall "api_internal".
Side information: In this project there a 3 different sections:
/api/internal/**** - should only be accessible from the website itself
/api/ - should be accessible via REST, is secured via JWTToken
the Website itself - is secured via form login and LDAP
Thankful for any help you can provide.
Max
In order to have hosts secured, use access_control
access_control:
# require ROLE_ADMIN for /admin*
- { path: ^/admin, roles: ROLE_ADMIN }
where the path is your desired host.
In order to have everything for a subhost secured use
- { path: ^/admin/*, roles: ROLE_ADMIN }
remove other firewalls than main and dev!
Please refer to http://symfony.com/doc/2.8/security.html for more information
I have been working on setting up the FOSUserBundle/RestBundle/OAuthServerBundle trio to create a headless back end that I can then place a separate front end on top of, and eventually expand to mobile, and possible third party API access. I have the general configuration in place based on the various resources and instructions available and can generate an access token using client credentials.
The application this is being added to is an existing one that uses standard Symfony/Twig for front end/back end interaction and uses FOSUserBundle for authentication.
I have two problems related to the flow of authentication.
I want the user to be able to access some parts of the API without authenticating past the client level, and some parts will require user-level authentication to verify they own the requested resources. I am not finding a way to do this. I've found posts talking about the possibility but nothing giving any direction on how it might be achieved. I believe I'll need to check at the controller level for appropriate access, maybe using custom voters, as checking for 'IS_AUTHENTICATED_FULLY' is coming back as true after just authenticating with the client. I want to be able to programmatically authenticate the user, bypassing the UI login form - that might just be overriding the FOSUserBundle login controller, but I'm not sure.
I either need to create a client without an access token expiration or find a way to implement the refresh token. I don't really see why my own app should need to refresh a token, but if that is the standard way to do it I'm ok with following specs on that.
Below is some relevant code, though by and large the code is pretty box standard stuff copied over from the FOSOAuthServer setup guide.
security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
oauth_token:
pattern: ^/oauth/v2/token
security: false
rest:
pattern: ^/rest(?!/doc)
fos_oauth: true
stateless: true
anonymous: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
success_handler: security.authentication.success_handler
use_referer: true
logout: true
anonymous: true
access_control:
- { path: ^/rest, roles: [ IS_AUTHENTICATED_FULLY ] }
config.yml snippet
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
registration:
form:
type: AppBundle\Form\Type\RegistrationFormType
profile:
form:
type: user_profile
fos_oauth_server:
db_driver: orm
client_class: AppBundle\Entity\Client
access_token_class: AppBundle\Entity\AccessToken
refresh_token_class: AppBundle\Entity\RefreshToken
auth_code_class: AppBundle\Entity\AuthCode
service:
user_provider: fos_user.user_provider.username_email
options:
supported_scopes: user
fos_rest:
view:
view_response_listener: force
formats:
json: true
templating_formats:
html: true
mime_types:
json: ['application/json', 'application/json;version=1.0', 'application/json;version=1.1']
jpg: ['image/jpeg']
png: ['image/png']
body_listener: true
param_fetcher_listener: true
allowed_methods_listener: true
format_listener:
rules:
- { path: ^/, priorities: [html, json], fallback_format: json, prefer_extension: false }
AD 1)
I solved your problem with two firewalls in security.yml.
Since Symfony is looking first match in security.yml I put first firewall to let anonymous users in:
api_anonym_area:
pattern: (^/api/forgotten-password/.*)
stateless: true
fos_oauth: true
anonymous: true
I catch URL with regex and give anonymous: true
As second firewall I have regex that catches all
api_auth_area:
pattern: ^/
fos_oauth: true
stateless: true
anonymous: false
So in your case, if you want anonymous users to get to /rest/doc, put in front of your firewall:rest something like this:
rest_doc:
pattern: ^/rest/doc
fos_oauth: true
stateless: true
anonymous: true
AD 2)
Its not good practice to have unlimited access token lifetime, but you can do it in config.yml by setting big integer to access_token_lifetime:
fos_oauth_server:
service:
options:
access_token_lifetime: #number in seconds#
To sign in with refresh token just
/oauth/v2/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
its in FOSOAuthServerBundle out of the box