Search code examples
restsymfonyfosrestbundlesymfony5

symfony 5 custom authentication for API REST


I'm working on a project with Symfony 5. I created the User entity, created the authentication flow on security.yaml and all works well: if user wants to access to protected area, login page was shown and authentication process works! So good!

Now, I want to build an API REST with FOSRest Bundle. I've created a specific controller for expose some routes:

/**
 * @Rest\Route("/api")
 *
 */

class APICustomController extends AbstractController
{
    ...
    /**
    * @Rest\Get("/shoes")
    * @param Request $request
    * @Method({"GET"})
    *
    * @return JsonResponse
    */
    public function getShoes(Request $request){

        ....
        return JsonResponse::fromJsonString(array('msg' => 'OK'));

    }

}

Here my security.yaml

security:
    enable_authenticator_manager: true
    encoders:
        App\Entity\User:
            algorithm: auto

    role_hierarchy:
        ROLE_ADMIN: ROLE_USER
        ROLE_VIEWER: ROLE_USER

    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: username
        api_user_provider:
            entity:
                class: App\Entity\User
                property: api_token
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            lazy: true
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\DealmapLoginAuthenticator
            logout:
                path: app_logout

        api:
            stateless: true
            lazy: true
            guard:
                authenticators:
                    - App\Security\TokenAuthenticator
            pattern: ^/api/
            provider: api_user_provider

    access_control:
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api, role: IS_AUTHENTICATED_FULLY }

I followed the steps given here: https://symfony.com/doc/current/security/guard_authentication.html

The problem is the call below

curl -X GET \
  http://www.example.com/api/shoes \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'x-auth-token: test'

it is protected by the main firewall (so it returns me the login page), and not by api as expected. I expect to receive an error message in json format.

What's wrong??

Thanks in advance


Solution

  • OK, I found the solution!

    I'm posting it here in case someone might need it in the future.

    The configuration was all correct, but the reason why the path /api/shoes was managed by the main firewall was due to the order of execution of the rules.

    The firewall main handles all the rules, while the api one handles only the ones with the ^/api pattern, so the most stringent rules should go first in the firewall definition in the security.yaml, as below:

    security:
        enable_authenticator_manager: true
        encoders:
            App\Entity\User:
                algorithm: auto
    
        role_hierarchy:
            ROLE_ADMIN: ROLE_USER
            ROLE_VIEWER: ROLE_USER
    
        providers:
            # used to reload user from session & other features (e.g. switch_user)
            app_user_provider:
                entity:
                    class: App\Entity\User
                    property: username
            api_user_provider:
                entity:
                    class: App\Entity\User
                    property: api_token
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            api:
                stateless: true
                lazy: true
                guard:
                    authenticators:
                        - App\Security\TokenAuthenticator
                pattern: ^/api/
                provider: api_user_provider
            main:
                lazy: true
                provider: app_user_provider
                guard:
                    authenticators:
                        - App\Security\DealmapLoginAuthenticator
                logout:
                    path: app_logout
    
        access_control:
            - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/api, role: IS_AUTHENTICATED_FULLY }