Di Login, Redirect, Intended cover image

Di Login, Redirect, Intended

alberto • February 15, 2022

focus

Una cosiddetta Login, è un processo tramite il quale un utente viene in qualche modo identificato all'interno di un portale web in modo da poter accedere a risorse private che, senza questa autenticazione sarebbero di fatto inaccessibili.

Un processo che permette il Login è solitamente suddiviso in diverse componenti:

In questo articolo di approfondimento, non analizzeremo questo processo nella sua interezza, ma ci soffermeremo su un piccolo aspetto, talvolta trascurato, ovvero dove viene rediretto l'utente dopo essersi autenticato.

Doppia modalità di accesso

Un utente puó arrivare alla pagina di login tramite due approcci:

Il percorso intrapreso dall'utente puó sembrare irrilevante ma di fatto non lo è. Quello che cambia è infatti il comportamento da egli atteso dopo aver effettuato l'accesso. Nel primo caso ci si aspetta che il browser venga redirezionato verso una sorta di homepage o comunque una pagina generica. Nel secondo caso invece ci si aspetta che la pagina caricata sia effettivamente quella che il nostro utente stava cercando di visualizzare prima di effettuare la login.

Laravel permette di gestire il secondo caso tramite il concetto di intended, ovvero pagina prevista o attesa.

Un controller tipico

Un esempio triviale di LoginController potrebbe essere questo:

class LoginController {

    public function login(LoginRequest $request)
    {
        if (Auth::attempt([
            'email' => $request->email,
            'password' => $request->password
        ])) {

            $request->session()->regenerate();

            return redirect()->intended(route('homepage'));
        }

        return back()->withErrors([
            'email' => 'Credenziali errate',
        ]);
    }

}

Il codice è abbastanza semplice: tramite una Auth::attempt proviamo ad effettuare un accesso. In caso positivo rigeneriamo la sessione per evitare complicanze ed effettuiamo una redirect mentre in caso negativo ritorniamo alla form includendo un messaggio di errore generico.

La parte interessante di tutto questo è la modalità con la quale viene effettuata una redirect e in particolare l'invocazione del metodo intended, metodo che probabilmente non verrà utilizzato in altre occasioni dove è necessario effettuare una redirect.

Il metodo intended è super semplice:

    public function intended($default = '/', $status = 302, $headers = [], $secure = null)
    {
        $path = $this->session->pull('url.intended', $default);

        return $this->to($path, $status, $headers, $secure);
    }

Non fa altro che controllare se nella sessione è presente qualche valore alla chiave url.intended e fare una redirect alla url cosi, eventualmente defaultarla con $default se non è disponibile niente in sessione.

Una guardia con memoria

Ok, abbiamo un LoginController smart, che redirige l'utente in maniera intelligente. Ma chi si occupa di valorizzare quell'url.intended dentro la sessione utente?

Tutto questa "magia" avviene all'interno dell'ExceptionHandler e in particolare nel metodo unauthenticated che si occupa appunto di gestire eventuali accessi a pagine private per utenti non loggati.

    protected function unauthenticated($request, AuthenticationException $exception)
    {
        return $request->expectsJson()
                    ? response()->json(['message' => $exception->getMessage()], 401)
                    : redirect()->guest($exception->redirectTo() ?? route('login'));
    }

Nel caso in cui la richiesta non sia una chiamata API viene invocato il metodo guest che come vediamo sotto, salva all'interno della sessione l'url desiderato dall'utente, pronto per essere letto una volta effettuato il login.

    public function guest($path, $status = 302, $headers = [], $secure = null)
    {
        $request = $this->generator->getRequest();

        $intended = $request->method() === 'GET' && $request->route() && ! $request->expectsJson()
                        ? $this->generator->full()
                        : $this->generator->previous();

        if ($intended) {
            $this->setIntendedUrl($intended);
        }

        return $this->to($path, $status, $headers, $secure);
    }

    public function setIntendedUrl($url)
    {
        $this->session->put('url.intended', $url);

        return $this;
    }

I misteri non esistono

Abbiamo svelato uno dei misteri di Laravel. Misteri che in fondo non sono mai dei veri misteri, ma semplicemente comportamenti e logiche ben organizzate che fanno sembrare semplici anche cose che magari non lo sono.

Mai soffermarsi quindi sul funziona ma ogni tanto addentriamoci anche sul perchè funziona.

Alla prossima.