0

I'm trying to setup routes for my Symfony2 single page app and I'm not sure how to go about it correctly without it feeling super hacky.

Here is what I need it to do and how I've attempted to set it up:

When Authenticated

  • Any route requesting application/jsonshould hit the routes they have been setup for as usual.

  • Any route that is entered not requesting application/json should load a controller that renders a twig file containing all the JS for my single page app.

  • Any static resource that doesn't exist and ends looking for a symfony route eg [.js, .css, .jpeg, etc] should return 404.

When NOT Authenticated

  • Anything requesting application/json should return 403

  • Anything NOT requesting application/json should return to the login page

Here is what i've attempted so far:

  • Setup routes with the FOSRestBundle for each service

  • Setup a listener that returns the base controller html if the request isn't application/json

    if (!in_array('application/json', $request->getAcceptableContentTypes())) {
        $fakeRequest = $event->getRequest()->duplicate(
            null,
            null,
            array('_controller' => 'HvHDashboardBundle:Dashboard:index')
        );
        $controller = $this->resolver->getController($fakeRequest);
        $event->setController($controller);
    }
    
  • Setup a bunch of 'catch all' routes to fake a 404 if the static resource doesn't exist.

    # routing.yml
    # Catch any files that are meant to be their own static resource and return 404  
    catch_all_fail:
        pattern:  /{uri}.{_format}
        defaults: { _controller: MyBundle:Dashboard:return404 }
        requirements:
            _format: js|hbs|css|jpg|gif|jpeg|png
    

Issues

  • This approach feels like a massive hack, and is not how the Symfony routing system is intended to work
  • The base controller page is returned even if you aren't authenticated because the type listener is being hit before the security context and forcing that controller to render.

Question:

How do other solve this issue with routing and single page apps with Symfony where they initially need to render HTML with twig, then JS takes over and requests JSON?

greg
  • 6,853
  • 15
  • 58
  • 71

1 Answers1

1

Only make an API, no static pages at all. Trust me, I recently did a moderate sized API with Symfony and that's the way to go. It will simplify your backend security a lot, if you do not mix the API with static pages. Ofcourse you can still have static pages, if you want to have some sort of Landing page or something. but try not to mix them with the main app.

Even for login, do not make a static page, but instead have an api route that will validate username/password and return the user the auth token in response. One user can have multiple tokens for example (can be logged in at multiple locations), and the token is sent in the request headers everytime.

If the token is validated ok, symfony will know which user it belongs, so you will know the 'User'. If no token is present, it should return "Not Authenticated", and if token is invalid also something like that or 'Bad request'.

One thing that I had to do when doing with APIs is that I had to write a request listener to accept JSON content and transform it into request object, so I could access data with $request->request.

If you have any questions, let me know in comment and I can help.

As far as routing is concerned, follow the REST rules and you will be good to go.

tomazahlin
  • 2,137
  • 1
  • 24
  • 29
  • Thanks. Do you recommend running the api on `api.domain.com` and serving the static login / landing page on a separate app pointed at `domain.com`? – greg Sep 18 '14 at 16:29
  • I do not know why you would need a static login form if you make an API, but in general it is a good practice to have different parts of the system on different subdomains. We do that here too. It also simplifies the usage of the firewall in the symfony2 config. For example everything in api.domain.com requires auth token, and domain.com allows anonymous token. – tomazahlin Sep 18 '14 at 16:46
  • Maybe I'm going about this wrong, but I need some way for the user to log into the app? That needs to happen in some sort of form.. At the moment the login is session based, which isn't ideal, I guess that needs to change to return a token? – greg Sep 18 '14 at 17:37
  • The login does not need to be form based. Simply, have a UserController, with an action loginAction(). Url: api.domain.com/login. Here you take username and password from POST data and then use UserProvider to get the matching user from the database. If the user exists, create a token for that user (usually a generated string value) and persist it into the user entity. – tomazahlin Sep 18 '14 at 19:29
  • ... (or make one-to-many relation), then return the token in response. Your javascript will store that token in localStorage and send it with every http request. By the token received in the backend you will always know which user it belongs to and authenticate that user user. This is called stateless authentication. So no session used, which is better in case of an API. When you want to logout make a DELETE request to let's say api.domain.com/logout, with the token in headers, and delete the token from database.:) – tomazahlin Sep 18 '14 at 19:30