I followed the documentation, and this is how my code is organized:
src/Company/AuthBundle/Security/ApiKeyAuthenticator.php
src/Company/AuthBundle/Security/ApiKeyUserProvider.php
(they are the same as sample classes in doc)
# File: services.yml
apikey_authenticator:
class: Company\AuthBundle\Security\ApiKeyAuthenticator
public: false
# File: security.yml
firewalls:
api:
pattern: ^/api
stateless: false
simple_preauth:
authenticator: apikey_authenticator
provider: fos_userbundle
security: false
anonymous: true
And now, If I try to access e.g. http://symfony-project.dev/api?apikey=somekey, I got the following error:
No route found for "GET /api"
Any idea what's wrong?
The error message is fairly clear really - you haven't defined a route for a GET request for '/api'.
The security component you've configured takes care of authentication and granting access, but it does not control any logic specific to your application. You must create a controller and a route for requests like this.
You can see your currently defined routes with $ php app/console debug:router - if you've tried to define a route, that will help you debug it. If not, you must create one.
Related
I have a static website where its pages may only be accessed if a user has authenticated via SAML2 SSO. Specifically, these pages are written with Twig, and the content is stored in JSON files which are fed in as variables to the Twig templates.
I was wondering if there was a simple way to leverage a PHP framework like Symfony to do this. Ideally, there would also be no database layer. Once a user has authenticated some cookie should be set that just permits them to cruise around as needed.
My background is with Drupal so that's why I'm looking in the direction of Symfony.
I do realize this question is kinda broad, so if there is a more appropriate place to inquire about this then please vote to close and point me in the right direction.
I've completed this functionality, posting my solution in the event this is useful to someone else down the line...
For a Symfony 5 project, I used https://github.com/hslavich/OneloginSamlBundle. Fill in config/packages/hslavich_onelogin_saml.yaml per the package's README.md, and according to how your SP and IdP are configured. One pro tip, the baseurl configuration value should be set to the application domain with /saml concatenated on to it (e.g. http://myapp.com/saml), there is a bug which strips off everything between the last path value (acs in /saml/acs) and the domain.
Update config/packages/security.yaml to look something like:
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
saml_provider:
saml:
user_class: App\Security\User
default_roles:
- ROLE_USER
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
app:
saml:
provider: saml_provider
# Match SAML attribute 'uid' with username.
# Uses getNameId() method by default.
username_attribute: eduPersonTargetedID
# Use the attribute's friendlyName instead of the name
check_path: saml_acs
login_path: saml_login
logout:
path: saml_logout
main:
lazy: true
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# 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: ^/saml/login, roles: PUBLIC_ACCESS }
- { path: ^/saml/metadata, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
The net result is /saml/login and /saml/metadata are publicly available, while all other routes require the ROLE_USER role. Upon a successful authentication with the IdP, the user is redirected back and is granted a session, and can then access all routes within the site.
in my Symfony 4 project I am using default FOSUSerBundle configuration which looks llike this:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/login #problem here
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
success_handler: fos_authentication_handler
failure_handler: fos_authentication_handler
logout: true
anonymous: true
So when I use pattern: ^/ it works fine, but when I do it like this: pattern: ^/login it prevents me from logging in saying that variable _SESSION is undefined. In my controller I am using the following code which works fine when firewall pattern is '^/':
$this->get("session")->save();
I dont want it to block the base route of my site ('mysite.com/') and I want to implement custom logic there. Any ideas how to fix it would be welcome. Thank you.
issue solved
finally I undertand that firewall pattern means pattern which covers all routing naming for the entire site. If it starts with '^/login', all other routes starting not with login wont be covered by firewall and thus error appears. Moreover, I got my index path under restricted access in security.yml so it threw the following error.
I want to include the logged in user's id to my logger.
So I have added a monolog.processor that adds the user id to the 'extra'-portion of the record, and added a custom format string that displays the id.
On my dev environment this works (mostly) as expected, but on the test environment it does not work at all, the TokenStorage always returns null on getToken().
There are no specific security configs for dev or test. The biggest differences between the configs is this part:
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
I have add this to my dev config but could not reproduce the symptoms. I can only reproduce by making symfony think it really is in test.
To be honest, I don't even know where to begin to debug this.
Any ideas what might be causing this behaviour?
Any ideas how I could debug this so I can get to an answer?
In order to have a token you should be inside on of the symfony firewalls.
If any of the firewalls aren't matched by the URI, symfony security is not triggered and you will not have a token.
If it is a public area allow anonymous users from root '/*' and use ACL for the rest of the URI (or actions). Anonymous users will have the role IS_AUTHENTICATED_ANONYMOUSLY
# app/config/security.yml
security:
firewalls:
main:
pattern: ^/
anonymous: ~
Documentation:
http://symfony.com/doc/current/security.html
I am currently trying to build a (hopefully fast) Website in Symfony2. I am using the FOSUserBundle to manage Users easily. I wrote a custom CacheManager to store Entities in Redis and to fetch back valid Entities that are managed by Doctrine. My custom UserManager uses this Service to provide Users Data faster. That all works quiet well, but in fact the UserManager seems to be ignored. Every Request results in a DB query.
My config for the FOSUserBundle (sry. had to replace the Project Name by 'XXX'):
fos_user:
db_driver: orm
firewall_name: main
user_class: XXX\MainBundle\Entity\User
registration:
form:
type: XXX_user_registration
validation_groups: [XXXRegistration]
confirmation:
enabled: true
template: XXXMainBundle:E-Mail:registration.email.html.twig
from_email:
address: registrierung#XXX.XXX
sender_name: XXX Registrierung
profile:
form:
type: XXX_user_profile
validation_groups: [XXXProfile]
resetting:
email:
template: XXXMainBundle:E-Mail:resetting.email.html.twig
from_email:
address: reset#XXX.XXX
sender_name: XXX.XXX
service:
mailer: fos_user.mailer.twig_swift
user_manager: XXX_main_bundle.security.user_manager
My service config:
XXX_main_bundle.security.user_manager:
class: XXX\Bundle\MainBundle\Security\UserManager
arguments: [#XXX_main_bundle.cache_manager, #fos_user.entity_manager, #fos_user.util.canonicalizer.default, #security.password_encoder]
My security config:
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
ROLE_DEVELOPER: ROLE_SUPER_ADMIN
firewalls:
main:
pattern: .*
form_login:
provider: fos_userbundle
check_path: /login_check
login_path: /login
logout:
path: /logout
anonymous: true
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login$
security: false
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
I can't show you my UserManager and my CacheManager, but if call and use them manually they are working fine! I also tried to write a custom UserProvider with dependency on the UserManager. But that always results in the Error:
ServiceNotFoundException in CheckExceptionOnInvalidReferenceBehaviorPass.php line 59:
The service "security.authentication.manager" has a dependency on a non-existent service "security.user.provider.concrete.fos_userbundle".
If I enable both UserProviders the Error disappears but the UserManager ist still ignored.
It seems to be used if i am using the built-in Commands of the FOSUserBundle but not when Symfony2 loads the User from the current Session.
Sry. for my bad English and maybe dumb Question but I am open for any criticism.
thx,
Justus Klein
EDIT 1: Seems like my UserManager isn`t completely ignored. It returns the User from the Cache but Symfony2 still triggers a DB Query to fetch the User.
EDIT 2: Found out that the refresh function of the UserProvider from the FOSUserBundle loads the User itself from the DB and not through the UserManager (wtf?). So overriding the UserProvider seems to make the clue. But I still get the following Error if I replace it:
ServiceNotFoundException in CheckExceptionOnInvalidReferenceBehaviorPass.php line 59:
The service "security.authentication.manager" has a dependency on a non-existent service "security.user.provider.concrete.fos_userbundle".
EDIT 3: I was able to override the UserProvider by giving it the same key as the one from the FOS Bundle:
security:
providers:
fos_userbundle:
id: xxx_main_bundle.security.user_provider
That canĀ“t be best Practice ^^ No everything works fine. I think thats the best & fastest way to store the User.
The best solution would be to enable the Second Level Cache feature available in Doctrine ORM 2.5, which performs exactly that: it caches entities in Redis (or any other cache you configure) to retrieve them much faster.
This will not help much when FOSUserBundle loads the user by username (because the ORM uses the id as identifier in its SLC, not the username), but this happens only when submitting the login form, or when reading a remember-me cookie if you use this feature. However, it will work for refresh where the user is loaded by primary key (and also in every other place of your project needing to load a user by primary key). Doctrine will also take care of updating the cache when the user is updated so you get valid data (be sure that you don't edit the user without using the ORM though).
And if you want to optimize the loading of user by username, you could write your own UserProvider which could keep a mapping between usernames and primary keys in Redis and then ask Doctrine for the User object by primary key (which would read it from the ORM cache then), while keeping a refreshing by primary as done in FOSUserBundle own provider (this is done for security reasons). This service can then either use the UserManager or Doctrine directly to load the user by username in case it is not yet in your username-to-id cache.
Overwriting the UserManager would not be needed anymore here, as it would not be used anymore in the critical path of the authentication.
I am creating a REST-API and currently have Basic Auth defined as authentication.
Symfony firewall config:
secured_area:
pattern: ^/
anonymous: false
http_basic:
realm: "Secured Basic Auth Area"
This leads to the correct behaviour if I do an unauthenticated request I get the correct header:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Secured Basic Auth Area"
...
But the problem is now that if I do a request via the browser i always get the Basic Auth popup being shown. To prevent this I though I could use the unauthorized_challenge config of the FOSRestBundle to change to custom authenticate challenge. Tough it does not change the returned header.
Config for rest bundle:
fos_rest:
unauthorized_challenge: "xxxBasic realm=\"Foo Area\""
access_denied_listener:
json: true
FOSRestBundle version is 1.4.2, Symfony version is 2.3.18.
Any idea what could be the problem that the FOSRestBundle setting is ignored?
Got it solved, the problem was:
I had an kernel.request listener which was throwing an AuthenticationException, this seemed to lead to the problem that further propagation of the event failed and the RestBundle listener was not properly called.
For completeness, after that got fixed I encountered another problem that the response always contained the Exception as HTML output. To solve this I also had to configure the FOSRestBundle exception controller in the Twig config.
My app/config.yml looks now like this (for the appropriate parts):
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
# Handle expections via the FOSRestBundle
exception_controller: 'FOS\RestBundle\Controller\ExceptionController::showAction'
fos_rest:
unauthorized_challenge: "xxxBasic realm=\"Foo Area\""
access_denied_listener:
json: true