0

This is a Symfony 3.4 application with a website frontend and a mobile app accessing the same backend. Users can log in by

  1. submitting a username and password (form login)
  2. authentication with a google account
  3. authentication with a facebook account

Previously, there was only one way to log in (username+password). Authentication and the firewall configuration worked. Adding the social network authentication required changes in the firewall configuration and now the guard authentication is partly broken.

What I'm confident about so far is that we need separate firewalls for the web users (main) and mobile app users (api). Web users are authenticated once and then the logged in user info is stored in the session cookie but the mobile app users are authenticated with every incoming request. When mobile users log in successfully, they get a jwt token as a response which they will send with every subsequent request.

What seems to be the biggest problem is the rest of the firewall configuration. In the current configuration, the "main" firewall works as intended. But something about the "api" firewall for mobile users is broken. Logging in works with the same guard authenticators so that they return the jwt token as expected. But the subsequent requests that are sent with the token all result in a 403 access denied response. I suspect that lexic authenticator never gets the jwt token from the request so it looks like the user never logged in.

The authenticators have been tested and they seem to work correctly for both web users and mobile users. The configuration of lexik jwt authenticator is also correct - or it hasn't changed since the time mobile users still had a single authenticator. This means the keys, pass phrase, token ttl.

An idea that might work would be to have a separate firewall for the mobile login urls and the rest of the mobile routes because they're handled by different authenticators. I tried it without any improvement in the situation: logging in works but jwt authentication doesn't. The relevant parts of security.yml below:

security:
...
  providers:
    db_users:
      entity: { ... }
  firewalls:
    dev:
      pattern: ^/(_(profiler|wdt)|css|images|js|api)/(password/reset)
      security: false
    api:
      pattern: ^/api/
      stateless: true
      lexik_jwt: ~
      anonymous: false
      guard:
        provider: db_users
        authenticators:
          - main.form_login_authenticator
          - main.google_login_authenticator
          - main.fb_login_authenticator
          - lexik_jwt_authentication.jwt_token_authenticator
        entry_point: main.form_login_authenticator
    main:
      pattern: ^/
      guard:
        provider: db_users
        authenticators:
          - main.form_login_authenticator
          - main.google_login_authenticator
          - main.fb_login_authenticator
        entry_point: main.form_login_authenticator
      form_login:
        remember_me: true
        login_path: login
        check_path: login
        always_use_default_target_path: true
        default_target_path: /redirect
        target_path_parameter: _target_path
        use_referer: false
        require_previous_session: false
      anonymous: true
...

What's broken here? How should I debug this issue? (other than using postman to emulate json requests from the mobile app)

Additional info: All three custom authenticators create a jwt token, for example:

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
  {
    // mobile users:
    if ( $request->getRequestFormat() == 'json') {
      return new JsonResponse(['token' => $this->jwtManager->create($token->getUser())]);
  // web users handled here

  }

Then about the access patterns (or how the request is handled and by which authenticator), here's an example from main.form_login_authenticator

public function supports(Request $request)
{
    return ('login' === $request->attributes->get('_route') || 'api_login_check' === $request->attributes->get('_route'))
        && $request->isMethod('POST');
}

The authenticators seem to work as intended, though. Logging in works. What doesn't work is staying logged in with the jwt token.

Miro Lehtonen
  • 609
  • 6
  • 18

2 Answers2

1

Finally it works. The main idea of the solution was to separate the firewalls: one for mobile login routes and another for the rest of the mobile (api) routes. Here's the configuration that works, omissions marked with three dots. main firewall configured as shown in the question.

security:
  ...
  firewalls:
    ...
    api_login:
      pattern: ^/api/(login|google-login|fb-login)
      stateless: true
      anonymous: true
      guard:
        provider: db_users
        authenticators:
          - main.form_login_authenticator
          - main.google_login_authenticator
          - main.fb_login_authenticator
        entry_point: main.form_login_authenticator
    api:
      pattern: ^/api
      stateless: true
      guard:
        provider: db_users
        authenticators:
          - lexik_jwt_authentication.jwt_token_authenticator
    main: 
      ...

The prefix of the authenticator service names originally came from the name of the firewall main which doesn't make sense anymore as they're shared between multiple firewalls now.

Miro Lehtonen
  • 609
  • 6
  • 18
0

First, for the api firewall you should only have the jwt authenticator because even if the auth. Is done with social network you must generate your own jwt token.

Secondly, can you send us your access pattern ? This can give us more clues.

Third, iam not sure that the entrypoint for the api firewall should be the same that the form one (https://symfony.com/doc/current/components/security/firewall.html#entrypoint)

  • Ok, so maybe the api firewall (jwt authenticator) should be separated from an api-login firewall (the other authenticators). I'm also not sure if the entry point is defined correctly. – Miro Lehtonen Nov 15 '19 at 05:14
  • I have just seen thé anonymous false in your api firewall, you must set it to True, because This is the jwt authenticator who do the authent. – Yann Schepens Nov 15 '19 at 07:43
  • Set anonymous true and the authentication exception is replaced with an info message on the security channel: Populated the TokenStorage with an anonymous Token. However, the problem remains. Mobile users don't seem to be logged in because their token is not found in the token storage (or anywhere). – Miro Lehtonen Nov 15 '19 at 10:36
  • Just to be sûre, when you receive the token, you send him back in the request headers (headers authorisation :bearer etc...) ? (lexik do not do it automatically). – Yann Schepens Nov 15 '19 at 18:49
  • Hum just a question, do you use apache 2.4? Because hé remove automatically Authorisation header : https://stackoverflow.com/questions/26475885/authorization-header-missing-in-php-post-request – Yann Schepens Nov 15 '19 at 18:51
  • Yes, the tokens are sent back and forth like they should. It worked with a previous firewall configuration (before adding more authenticators for google and facebook) and the fix for the apache issue is in place. – Miro Lehtonen Nov 16 '19 at 05:27