9

Let's say I have an invoice entity. Invoice belongs to some user (invoices.user_id).

If the user enters myapp.com/invoices/1 he needs to sign in to gain access to his invoice. That's pretty normal.

Sometimes invoices.user_id is null (invoice owner doesn't have an account in our system), but we have an invoices.phone_number column.

The goal is to create an authentication system based on SMS code verification for users that don't have the account in our system. If the user confirms that he indeed owns phone number related to the invoice (code verification) I want to grant him temporary access (15 min) to this invoice details page (and only this page).

My first idea was to use a JWT token stored in the session.

My second idea was to use a custom firewall.

Is there any better approach?

Aram Grigoryan
  • 740
  • 1
  • 6
  • 24
Kamil Latosinski
  • 756
  • 5
  • 28
  • Seems you are looking for a scenario of a signed url. This package might help: https://github.com/spatie/url-signer – Pubudu Jayawardana Jul 30 '18 at 14:42
  • Unfortunately, according to requirements, sms verification is a must. But thanks anyway, I didn't know the term signed url! – Kamil Latosinski Jul 30 '18 at 15:14
  • I'm curious to know what the goal is here; is it to confirm that an authenticated user is who they say they are, or is it to verify that they are actual human beings? what are the specific concerns that you are trying to address with this solution? – William Perron Aug 02 '18 at 12:46
  • @WilliamPerron I have edited the question. It should be crystal clear what the goal is! – Kamil Latosinski Aug 02 '18 at 13:23
  • 1
    Are you using JWT for rest of your Authentication and Authorization? What I am wondering is, why cant you use the existing Access control mechanism of your app to Authenticate and control access to this page too. What I meant to say is, Instead of logging in with the userid+password, user will login using phonenumber+smscode and then you will login the user for 15 mins. – so-random-dude Aug 08 '18 at 06:16
  • @so-random-dude the problem is there is no user in users table in the second case. In some cases I have only some phone number that is not related to any concrete user from users table. This is why existing mechanism won't fit. Obviously some human being owns this phone number and because of this I would like to give him access even if he doesn't have an account in my system. – Kamil Latosinski Aug 08 '18 at 09:50

2 Answers2

7

Create a kernel.request listener. This way you can act, before anything is executed, and whole application is oblivious to the fact that the user can be logged out any minute.

Call a "service" which will validate the token. If the token is not valid, clear authentication status and override the request. For instance, redirect the user to a "you need to pay again" page.

This way you don't need to modify any code, execute any voters and so on, your whole application can be protected.

As for the authentication itself, go for a custom guard, where you can fully control how the authentication process will work.

Mike Doe
  • 16,349
  • 11
  • 65
  • 88
2

You can authenticate a dummy user for 15 minutes using the following action:

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

public function indexAction(Request $request)
{
    $em = $this->getDoctrine()->getManager();

    /**
     * confirm that the user indeed owns 
     * phone number related to the invoice (code verification)
     */

    //create a user for this task only and fetch it
    $user = $em->getRepository(User::class)->find(1);

    //firewall name used for authentication in security.yml
    $firewall = "main_secured_area";

    $token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
    $this->get('security.token_storage')->setToken($token);
    $this->get('session')->set("_security_$firewall", serialize($token));

    //$lifetime takes number of seconds to define session timeout 15min = 900sec
    $this->container->get('session')->migrate($destroy = false, $lifetime = 900);

    //fire the login event manually
    $event = new InteractiveLoginEvent($request, $token);
    $this->get("event_dispatcher")->dispatch("security.interactive_login", $event);

    return $this->render('default/index.html.twig');
}