How to authenticate/authorize anonymous user for a limited time?
Asked Answered
F

2

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?

Fillister answered 30/7, 2018 at 14:16 Comment(6)
Seems you are looking for a scenario of a signed url. This package might help: github.com/spatie/url-signerCentiare
Unfortunately, according to requirements, sms verification is a must. But thanks anyway, I didn't know the term signed url!Fillister
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?Unveiling
@WilliamPerron I have edited the question. It should be crystal clear what the goal is!Fillister
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.Tobias
@Tobias 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.Fillister
W
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.

Wie answered 1/8, 2018 at 14:25 Comment(2)
Although I've solved it a bit differently, you have put me on the right track. I'll add my final solution to the question. Thanks!Fillister
Happy to help Kamil.Wie
R
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');
}
Ruisdael answered 5/8, 2018 at 18:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.