Symfony Security: Auth with session or oauth - php

I have developed a REST API, there are two ways to connect to it: session and oauth.
Basically, my website will use the session mode and third-party softwares will use the oauth mode.
I managed to make make both session and oauth modes to work in symfony, but I can't make them work at the same time.
Here is my firewalls security config:
firewalls:
auth_oauth_token:
pattern: ^/auth/oauth/v2/token
security: false
api:
pattern: ^/api
anonymous: false
fos_oauth: true
stateless: true
auth:
pattern: ^/
anonymous: ~
form_login:
login_path: /auth/session/check
check_path: /auth/session/login
always_use_default_target_path: true
default_target_path: /auth/session/check
failure_path: /auth/session/check
failure_forward: false
use_forward: false
failure_forward: false
username_parameter: username
password_parameter: password
post_only: true
remember_me: false
require_previous_session: false
logout:
path: /auth/session/logout
target: /auth/session/logged_out
invalidate_session: false
Session handling: /auth/session.
OAuth handling: /auth/oauth.
Api: /api.
So, with this config, with "api" firewall first, I can log in with a token.
But even logged in with a session, if I don't specify the token, I won't have access.
With "auth" firewall first, I can log in with the session form.
But even if I specify a token, I won't have access.
I'm getting crazy with this. I found on stack overflow something about chain providers, I would probably need something like "chain firewall"... if forbidden, check another firewall.
Thank you

I solved by duplicating the routes of the api controllers, so that I have a route /api/method which relies on OAuth2, and a /webapi/method route which relies on the standard (main) firewall:
In security.yml:
firewalls:
api:
pattern: ^/api
fos_oauth: true
stateless: true
oauth_token:
pattern: ^/oauth/v2/token
security: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /login
check_path: /login_check
logout: true
anonymous: true
access_control:
- { path: ^/api, roles: [ IS_AUTHENTICATED_FULLY ] }
- { path: ^/web-api, roles: [ IS_AUTHENTICATED_FULLY ] }
In routing.yml:
acme_api:
type: rest
prefix: /
resource: "#AcmeBundle/Resources/config/routing_api.yml"
In routing_api.yml:
# REST API - OAUTH Access
acme_api_users:
resource: AcmeBundle\Controller\UsersController
type: rest
defaults: {_format: json}
prefix: /api
name_prefix: api_
# REST API - Frontend Client Access
acme_webapi_users:
resource: AcmeBundle\Controller\UsersController
type: rest
defaults: {_format: json}
prefix: /web-api
name_prefix: webapi_

Related

How to connect from SonataAdmin authenticator to other authenticator?

I have a symfony application which has inside the Sonata Admin Bundle for the admin part, with it's own firewall (admin) and the firewall for the user part of the application (main).
At the moment, the admin which is connected with sonata can't access the API that is designed for the user because it is authenticated for the Sonata Admin Bundle authenticator and for the API it sees him as a null user or not authenticated one.
I want to allow the admin to access an API that is made for the part of the application that is behind the firewall for the user part.
Config for the firewalls in the security.yaml file:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin(.*)
form_login:
provider: app_user_admin
login_path: admin_login
use_forward: false
check_path: admin_login
failure_path: null
logout:
path: admin_logout
target: admin_login
anonymous: true
guard:
authenticators:
- App\Security\AdminLoginAuthenticator
main:
anonymous: true
logout:
path: security_logout
guard:
authenticators:
- App\Security\UserLoginAuthenticator
Is there a way to can connect the two authenticators for the admin? Like, on a success login for the admin to call the authenticator for the main firewall?
After some digging and some help, I found out that symfony security has something like this built in.
It's called Symfony context and does the exact same thing.
For future reference, this is what you really need to add to the config file:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
context: just_a_random_name
pattern: ^/admin(.*)
form_login:
provider: app_user_admin
login_path: admin_login
use_forward: false
check_path: admin_login
failure_path: null
logout:
path: admin_logout
target: admin_login
anonymous: true
guard:
authenticators:
- App\Security\AdminLoginAuthenticator
main:
context: just_a_random_name
anonymous: true
logout:
path: security_logout
guard:
authenticators:
- App\Security\UserLoginAuthenticator

Symfony 4: Do not redirect to /login for matching routes

I have a Symfony Application that has two primary sections. / (everything under root e.g. /userProfile) and /api/ (everything under the /api route e.g. /api/userInfo/3).
I'm using Twig to render the actual pages under root, and a simple JsonResponse to render everything under /api. Currently, when a user tries to access any page / resource when they are not logged in, they are redirected to /login and then to the page / resource they requested.
I'd like to alter this behavior for all resources under /api. What I'd like /api/whatever to do is either return the requested resource (if logged in), or return a 401 if not logged in. I'd like to continue to redirect to /login for all other routes.
(NOTE: the /api routes are not "RESTful" API routes per ce. They are "internal" API routes that the UI uses to request data it needs to render the various pages. So it's safe to assume that a user would have logged in via the normal login form prior to their client requesting one of these routes.)
Here's my security.yaml:
security:
providers:
db_provider:
id: database_user_provider
encoders:
App\Utility\Security\DatabaseUser: bcrypt
access_decision_manager:
strategy: unanimous
allow_if_all_abstain: false
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
context: main
form_login:
provider: db_provider
login_path: login
check_path: process_login
default_target_path: manage
use_referer: true
failure_handler: App\Utility\Security\AuthenticationFailureHandler
user_checker: App\Utility\Security\UserChecker
anonymous: ~
logout:
path: logout
target: login
access_denied_handler: App\Utility\Security\AccessDeniedHandler #Turn this off in dev.
# 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: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/forgot, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/%app.locales%, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
I've tried adding a new firewall context of api:
api:
pattern: ^/api
context: main
# If I don't include this (from "main" firewall), I get an error stating no UserAuthenticationListener has been provided for secuirty.firewall.api.
form_login:
provider: db_provider
login_path: login
check_path: process_login
use_referer: true
failure_handler: App\Utility\Security\AuthenticationFailureHandler
Symfony complains if I do not include login_path and check_path.
So, how do I tell Symfony to simply fail and return a 401 when a user is not logged in (or session has expired), when (and only when) they are accessing a route under /api?
Thanks.
To get this working, move the api firewall above main.
We can then set it up on security.yaml with the following structure:
api:
pattern: ^/api
context: main
stateless: false
anonymous: true
guard:
authenticators:
- App\Security\ApiAuthenticator
provider: db_provider
The other thing we need is an implementation of AuthenticatorInterface. e.g. the App\Security\ApiAuthenticator
We only need to implement the following two methods from the interface:
start and supports
public function start(Request $request, AuthenticationException $authException = null)
{
return new Response('', Response::HTTP_UNAUTHORIZED);
}
public function supports(Request $request)
{
return false;
}
Note: The above implementation was tested on Symfony 4 (4.3)
Add the path ^/api with the allowed roles in the access_control section https://symfony.com/doc/current/security.html#add-code-to-deny-access
This is how my security is looking. It's working the way you wanted, just that I use OAuth
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
oauth_token:
pattern: ^/oauth/v2/token
security: false
oauth_authorize:
pattern: ^/oauth/v2/auth
form_login:
provider: fos_userbundle
check_path: /oauth/v2/auth_login_check
login_path: /oauth/v2/auth_login
use_referer: true
api:
pattern: ^/api
fos_oauth: true
stateless: true
anonymous: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
logout: true
anonymous: true
guard:
provider: fos_userbundle
authenticators:
- App\Security\GoogleAuthenticator
- App\Security\FacebookAuthenticator
entry_point: App\Security\LoginFormAuthenticator
i would use the #Security annotaion
https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html

symfony 3 - how to allow unauthenticated access to a URL prefix

Symfony 3.0.3 I want to exclude URLs starting with /document from having to log in
My current security.yml firewalls:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
docs:
pattern: ^/document
security: false
main:
pattern: ^/
http_basic: ~
provider: our_db_provider
anonymous: ~
form_login:
login_path: /
check_path: login
logout:
path: /logout
target: /
invalidate_session: true
But this results in Error 500 : "The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL." when visiting /document
How is your access_control configuration in security.yml. Shouldn't you allow /document for IS_AUTHENTICATED_ANONYMOUSLY?
access_control:
- { path: ^/document$, role: IS_AUTHENTICATED_ANONYMOUSLY }
In this case, you don't have to define a separate firewall for /document.

Symfony session, different route, different session

So, i have two differents route on my project :
/memberarea
/mobile
The first is for the web version on my application, and the second is for the mobile version.
Here you can see a part of my security.yml :
firewalls:
main:
pattern: ^/
form_login:
login_path: /
provider: fos_userbundle
csrf_provider: form.csrf_provider
default_target_path: /memberarea
logout: true
anonymous: true
mobile:
pattern: /mobile/.*
logout: true
anonymous: true
access_control:
- { path: ^/memberarea, roles: IS_AUTHENTICATED_FULLY }
- { path: ^/mobile, roles: IS_AUTHENTICATED_FULLY }
My problem, when a user login on mobile, i create a session on symfony with the firewall mobile like : $token = new UsernamePasswordToken($user, $request->get('password'), "mobile", $user->getRoles());.....
this user can use all route in /mobile, it's ok. But he can use /memberarea too.
How can i do for login a user just for /mobile, just for /memberarea or for both ?
If I have correctly understood, you want to log into your mobile application with a different session than on your web application.
What I am doing in order to obtain this result is setting up in my security.yml file a different context for each firewall I have.
(If you want to have one session for both you must have the context with the same value for the given firewalls.)
File: app/config/security.yml
firewalls:
main:
pattern: ^/
**context: user**
form_login:
login_path: /
provider: fos_userbundle
csrf_provider: form.csrf_provider
default_target_path: /memberarea
logout: true
anonymous: true
mobile:
pattern: /mobile/.*
*context: mobile_user*
logout: true
anonymous: true
Hope this helped.

Symfony2.1: Unable to find the controller for path "/login_check"

I used the "Using a traditional login form" tutorial from symfony.com to authentificate my users. With a simple http auth it works great.
After the login was submitted I get this Exception:
Unable to find the controller for path "/login_check". Maybe you
forgot to add the matching route in your routing configuration?
Well, in the tutorial I read:
You will not need to implement a controller for the /login_check URL as the firewall will automatically catch and process any form submitted to this URL.
I defined the routes and set the firewall settings:
security.yml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
backend:
pattern: ^/backend
anonymous: ~
form_login:
provider: entity
login_path: /login
check_path: /login_check
#use_forward: true
logout:
path: /logout
target: /
routing.yml
login:
pattern: /login
defaults: { _controller: VitaSecurityBundle:Default:login }
login_check:
pattern: /login_check
logout:
pattern: /logout
The problem you are having is described here:
See http://symfony.com/doc/current/book/security.html, section "Avoid Common Pitfalls"
Be sure /login_check is behind a firewall
Next, make sure that your check_path URL (e.g. /login_check) is behind the firewall you're using for your form login (in this example, the single firewall matches all URLs, including /login_check). If /login_check doesn't match any firewall, you'll receive a Unable to find the controller for path "/login_check" exception.
In this example, your pattern specifies a prefix of /backend for secured paths. To work, your login check should be behind this same firewall.
So, to match the pattern which you have specified in your firewall, put login_check on a url path like this: /backend/login_check
I found the solution to my problem
I added the /backend prefix to my paths, removed the 'anonymous: ~' line and commented out the ACL for backend.
security.yml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login_firewall:
pattern: ^/backend/login$
anonymous: ~
backend:
pattern: ^/backend
form_login:
provider: entity
login_path: /backend/login
check_path: /backend/login_check
#use_forward: true
logout:
path: /backend/logout
target: /
access_control:
#- { path: ^/backend, roles: ROLE_USER }
routing.yml
login:
pattern: /backend/login
defaults: { _controller: VitaSecurityBundle:Default:login }
login_check:
pattern: /backend/login_check
logout:
pattern: /backend/logout
The problem also tends to happen when you have two firewall with the same pattern. For example:
....
backend:
pattern: ^/*
....
frontend:
pattern: ^/*
You must change one of the two as follows:
....
backend:
pattern: ^/(administrador|backend)/*
....
frontend:
pattern: ^/*
Here is a sample code I used in a real-life project:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/cmd
anonymous: ~
form_login:
check_path: /cmd/login_check
login_path: /cmd/login
remember_me:
always_remember_me: true
key: "%secret%"
path: /cmd
domain: ~ # Defaults to the current domain from $_SERVER
logout:
path: /cmd/logout
target: /
admin:
pattern: ^/admin
http_basic:
realm: "Administration"
free_area:
pattern: ^/
anonymous: ~
In my case, only the /cmd/ part is secured, the /admin/ part is also secured, but with HTTP security.
Maybe you should try:
security.yml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
backend:
pattern: ^/backend
anonymous: ~
form_login:
provider: entity
login_path: /backend/login
check_path: /backend/login_check
#use_forward: true
logout:
path: /backend/logout
target: /
and as of routing.yml:
login:
pattern: /backend/login
defaults: { _controller: VitaSecurityBundle:Default:login }
login_check:
pattern: /backend/login_check
logout:
pattern: /backend/logout
I think your problem might come from the fact security is not activated in your / part (the pattern of your secured area is ^/backend)
This was not workging for me and I try something else :
firewalls:
dev:
pattern: ^/(_profiler|_wdt|css|js)
security: false
login:
pattern: ^/login$
security: false
secured_area:
pattern: /(admin/.*|login_check)
provider: in_memory
form_login:
check_path: /login_check
login_path: /login
default_target_path: /admin
always_use_default_target_path: true
logout:
path: /logout
target: /
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_ADMIN }
With the following explanation, simpler than the explanation from zabojad.
The important thing is to put the login_check route inside a firewall and to let the login outside. With a or pattern you can succeed.
Max

Categories