0

I have Symfony2 application separated into 2 bundles: BackendBundle for API and FrontendBundle for AngularJS "client". Everything works under firewall.

BackendBundle has entities, handles API routes; FrontendBundle has Angular views, routing etc. and has only one controller with wildcard:

class AngularController extends Controller {
    /**
     * @Route("/{route}", name="angular_index_all_unmatched_routes", requirements={"route" = ".*"})
     * @Template("FrontendBundle::index.html.twig")
     */
    public function angularIndexAction($route) {
        return ['route' => $route];
    }
}

FrontendBundle routing is defined as last resource in app/config/routing.yml, to be invoked only if any other route was not matched. Thanks to that, it can handle Angular HTML5-mode routes if they're accessed directly (for example copy-paste) - and it works ok.

What I want to do, is define firewall and/or access control in way that all those unmatched routes (handled by AngularController::angularIndexAction()) could be accessible by anonymous user.

Why? I want to open some API routes (via frontend proxy) to be accessible by non-users (for example confirmation URLs sent by email, with some message to user).

I don't want to hardcode access control list for every anonymous "Angular" route, I would like to do it only for API routes. At the end, those unmatched routes should open Angular's index which should know if user is logged in (for displaying full or simplified layout) and should handle Angular routes and display some kind of "Access denied" message if request failed (there is Symfony listener and Angular's $provide interceptor for that).

Any suggestions?


Edit: @Security annotation on AngularController::angularIndexAction() does not work, it still redirects to firewall entry point.


Edit2: Here is fragment of security.yml

firewalls:
    unsecured:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
        anonymous: true

    secured:
        pattern: '^.*$'
        form_login:
            login_path: /our-provider/login
            check_path: /our-provider/callback/
        anonymous: true
        entry_point: our_provider.entry_point

access_control:
    - { path: '^/our-provider/(login(/[a-zA-Z]+)?|logout|redirect|callback)', roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: '^/', roles: ROLE_USER }

I know that { path: '^/', roles: ROLE_USER } will redirect all routes to login page if user is not logged in. I assumed it's obvious and did not mentioned it. What I want is force ROLE_USER for matched routes and let IS_AUTHENTICATED_ANONYMOUSLY for those unmatched, without explicitely defining each frontend "proxy-route". In my case there is not 404 Symfony page, because everything goes to angular_index_all_unmatched_routes route and there Angular routing definition decides if there is something to handle or not.

Wouter J
  • 41,455
  • 15
  • 107
  • 112
Wirone
  • 3,304
  • 1
  • 29
  • 48
  • Do you have any other routes besides `^/` that are not intended to be anonymous? – Darragh Enright Mar 16 '15 at 22:38
  • Yes, all `^/api/` for example. This is, for now, API with "pseudo-authorization", because it's based on the same firewall from Symfony security. So basically user has to log in to the app to use API (because it's only for frontend-backend communication right now, it will be changed if we'll open API for other clients). So firewall is set to `'^.*$` for automatic redirects to the login page for anonymous users. So let's sum it up: use firewall for existing Symfony routes, allow anonymous access for Angular routes, handled by `AngularController::angularIndexAction()`. – Wirone Mar 17 '15 at 12:50
  • I'm sorry I did not get time to check it yet, because it was job-related problem and we had to go to next issues and projects. I will back to it soon and let you now. – Wirone Mar 24 '15 at 22:14
  • No problem! Just curious :) – Darragh Enright Mar 25 '15 at 12:56

1 Answers1

0

I haven't tried this, and I cannot begin to guess your existing security/route setup in security.yml but I guess you could whitelist the method with IS_AUTHENTICATED_ANONYMOUSLY. From the Symfony docs:

All users (even anonymous ones) have this - this is useful when whitelisting URLs to guarantee access - some details are in How Does the Security access_control Work?.

So, for example, if you were using the @Security annotation you could do something like (not tested):

class AngularController extends Controller {
    /**
     * @Route("/{route}", name="route", requirements={"route" = ".*"})
     * @Template("FrontendBundle::index.html.twig")
     * @Security("has_role('IS_AUTHENTICATED_ANONYMOUSLY')")
     */
    public function angularIndexAction($route) {
        return ['route' => $route];
    }
}

More on the @Security annotation here.

Hope this helps :)

Edit

All that said, when you define/restrict your routes under access_control in security.yml, the matching process stops on the first match. I assume that you have some role-restricted paths, which you should define explicitly - and put them first, so if they match the process stops.

Otherwise, you should be able to add a catch-all route, enforced by role IS_AUTHENTICATED_ANONYMOUSLY. Since the path definition of a route is a regex, something like ^/ should catch anything that is not explicitly defined. Just make sure and place it after your restricted route definitions.

You would not need for the @Security annotation in this case.

Edit 2

I tried mocking this out using a clean instance and HTTP BasicAuth but what I was trying to achieve was the following, which I understand as similar to your use case:

  • Create a backend controller with routes / and /api/ and trigger a HTTP BasicAuth authentication popup
  • Create a frontend controller with route /{route} that would match everything else and authenticate anonymously.

My firewall and access_control configuration looks like this:

security:
    encoders:
        # encoder config here
    providers:
        # provider config here
firewalls:
    dev:
        pattern:  ^/(_(profiler|wdt)|css|images|js)/
        security: false
    secured:
        anonymous: ~
        http_basic: ~

access_control:
    - { path: ^/$,    roles: ROLE_USER }
    - { path: ^/api/, roles: ROLE_USER }
    - { path: ^/,     roles: IS_AUTHENTICATED_ANONYMOUSLY }

Access control paths are regexes, so ^/$ and ^/ are not the same. The former will only match exactly to route /. The latter will match any route that begins with /; e.g: /home, /products, /contact etc.

Indeed, the latter will match and anonymously authenticate /api, but it will not match /api/, or /api/1 etc. as these are explicitly defined and restricted to ROLE_USER.

So the general idea is to explicitly and (if possible) exactly match the routes you want to restrict, and declare those first. The last declaration ^/ should openly catch any other route that falls through.

Darragh Enright
  • 13,676
  • 7
  • 41
  • 48
  • I've already tested `@Security` annotation - does not work, it still redirects to login page (firewall entry point). Sorry for not mentioning it in description! I've tested both `@Security("has_role('IS_AUTHENTICATED_ANONYMOUSLY')")` and `@Security("is_granted('IS_AUTHENTICATED_ANONYMOUSLY')")` – Wirone Mar 13 '15 at 14:38
  • Hi @Wirone. Can you post your `access_control` definition as it might provide some hint? – Darragh Enright Mar 13 '15 at 14:39
  • I've edited my question, maybe you did not notice it :-) – Wirone Mar 16 '15 at 09:02