web-dev-qa-db-de.com

Aktualisieren von Authentifizierungsstoken für ein Vue.js-SPA mit Laravel für das Backend

Ich erstelle eine Single-Page-App mit Vue (2.5) und benutze Laravel (5.5) als Backend. Alles funktioniert gut, außer dass Sie sich nach dem Abmelden direkt wieder anmelden. In diesem Fall schlägt der Aufruf von/api/user (um die Kontoinformationen des Benutzers abzurufen und die Identität des Benutzers erneut zu überprüfen) mit einem nicht autorisierten 401 fehl (obwohl die Anmeldung erfolgreich war). Als Antwort wird der Benutzer direkt zum Anmeldebildschirm zurückgeleitet (ich habe diese Kennzahl selbst als Reaktion auf 401 Antworten geschrieben).

Sie müssen sich abmelden, die Seite mit Strg/cmd + R aktualisieren und sich dann erneut anmelden. Die Tatsache, dass eine Seitenaktualisierung mein Problem behebt, gibt mir Anlass zu der Annahme, dass ich die Aktualisierung des X-CSRF-TOKEN nicht korrekt bearbeite oder bestimmte Cookies, die Laravel verwendet (wie beschrieben Hier ).

Dies ist ein Ausschnitt aus dem Code des Anmeldeformulars, der ausgeführt wird, nachdem ein Benutzer auf die Anmeldeschaltfläche geklickt hat.

login(){
    // Copy the form data
    const data = {...this.user};
    // If remember is false, don't send the parameter to the server
    if(data.remember === false){
        delete data.remember;
    }

    this.authenticating = true;

    this.authenticate(data)
        .then( this.refreshTokens )
        .catch( error => {
            this.authenticating = false;
            if(error.response && [422, 423].includes(error.response.status) ){
                this.validationErrors = error.response.data.errors;
                this.showErrorMessage(error.response.data.message);
            }else{
                this.showErrorMessage(error.message);  
            }
        });
},
refreshTokens(){
    return new Promise((resolve, reject) => {
        axios.get('/refreshtokens')
            .then( response => {
                window.Laravel.csrfToken = response.data.csrfToken;
                window.axios.defaults.headers.common['X-CSRF-TOKEN'] = response.data.csrfToken;
                this.authenticating = false;
                this.$router.replace(this.$route.query.redirect || '/');
                return resolve(response);
            })
            .catch( error => {
                this.showErrorMessage(error.message);
                reject(error);
            });
    });
},  

die authenticate()-Methode ist eine vuex-Aktion, die den Anmeldeendpunkt auf der Laravel-Seite aufruft.

Der/refreshTokens-Endpunkt ruft einfach diese Laravel Controller-Funktion auf, die das CSRF-Token des aktuell angemeldeten Benutzers zurückgibt:

public function getCsrfToken(){
    return ['csrfToken' => csrf_token()];
}

Nachdem die Token erneut abgerufen wurden, wird der Benutzer mit this.$router.replace(this.$route.query.redirect || '/'); auf die Hauptseite (oder eine andere Seite, falls angegeben) umgeleitet, und dort wird die api/user -Funktion aufgerufen, um die Daten des aktuell angemeldeten Benutzers zu überprüfen.

Gibt es andere Maßnahmen, die ich ergreifen sollte, um diese Arbeit zu machen, die ich übersehen habe?

Danke für jede Hilfe!


BEARBEITEN: 07. November 2017

Nach all den hilfreichen Vorschlägen möchte ich einige Informationen hinzufügen. Ich verwende Passport, um mich auf der Seite Laravel zu authentifizieren, und die Middleware CreateFreshApiToken ist installiert.

Ich habe mir die von meiner App gesetzten Cookies angesehen, insbesondere den laravel_token, der das verschlüsselte JWT enthalten soll, das Passport zur Authentifizierung von API-Anforderungen von Ihrer JavaScript-Anwendung verwendet. Beim Abmelden wird das Cookie laravel_token gelöscht. Wenn Sie sich direkt danach erneut anmelden (indem Sie Axios zum Senden einer AJAX Post-Anfrage verwenden), wird kein neuer laravel_token festgelegt, weshalb der Benutzer nicht authentifiziert wird. Mir ist bekannt, dass Laravel das Cookie nicht auf die Anmeldeanforderung POST setzt, sondern die GET-Anforderung an/refreshTokens (die nicht geschützt ist) direkt danach sollte Cookie setzen. Dies scheint jedoch nicht zu geschehen.

Ich habe versucht, die Verzögerung zwischen der Anforderung von /refreshTokens und der Anforderung von /api/user zu erhöhen, um dem Server möglicherweise etwas Zeit zu geben, um die Dinge in Ordnung zu bringen, aber ohne Erfolg.

Der Vollständigkeit halber ist hier mein Auth\LoginController, der die Anmeldeanforderung serverseitig verarbeitet:

class LoginController extends Controller
{
    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        // $this->middleware('guest')->except('logout');
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(\Illuminate\Http\Request $request)
    {
        //return $request->only($this->username(), 'password');
        return ['email' => $request->{$this->username()}, 'password' => $request->password, 'active' => 1];
    }

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(\Illuminate\Http\Request $request, $user)
    {
        $user->last_login = \Carbon\Carbon::now();
        $user->timestamps = false;
        $user->save();
        $user->timestamps = true;

        return (new UserResource($user))->additional(
            ['permissions' => $user->getUIPermissions()]
        );
    }


    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(\Illuminate\Http\Request $request)
    {
        $this->guard()->logout();
        $request->session()->invalidate();
    }
}
7
Daniel Schreij

Endlich behoben!

Wenn Sie die UserResource direkt in der LoginControllers authenticated -Methode zurückgeben, handelt es sich nicht um eine gültige Laravel Antwort (aber ich vermute, dass JSON-Rohdaten vorliegen?), Sodass wahrscheinlich Dinge wie Cookies nicht angehängt werden. Ich musste einen Aufruf an response () an die Ressource anhängen, und jetzt scheint alles in Ordnung zu sein (obwohl ich umfangreichere Tests durchführen muss).

So:

protected function authenticated(\Illuminate\Http\Request $request, $user)
{
    ...

    return (new UserResource($user))->additional(
        ['permissions' => $user->getUIPermissions()]
    );
}

wird

protected function authenticated(\Illuminate\Http\Request $request, $user)
{
    ...

    return (new UserResource($user))->additional(
        ['permissions' => $user->getUIPermissions()]
    )->response();  // Add response to Resource
}

Ein Hoch auf die Laravel Dokumente zur Zuordnung eines Abschnitts zu diesem Thema: https://laravel.com/docs/5.5/eloquent-resources#resource-responses

Darüber hinaus wird das laravel_token nicht durch die POST -Anforderung zum Anmelden festgelegt, und der Aufruf von refreshCsrfToken () hat den Trick ebenfalls nicht ausgeführt, wahrscheinlich, weil es durch die Gast-Middleware geschützt wurde.

Am Ende hat es für mich geklappt, einen Dummy-Aufruf an '/' auszuführen, nachdem die Login-Funktion zurückgekehrt ist (oder das Versprechen erfüllt wurde).

Am Ende war meine Login-Funktion in der Komponente wie folgt:

login(){
    // Copy the user object
    const data = {...this.user};
    // If remember is false, don't send the parameter to the server
    if(data.remember === false){
        delete data.remember;
    }

    this.authenticating = true;

    this.authenticate(data)
        .then( csrf_token => {
            window.Laravel.csrfToken = csrf_token;
            window.axios.defaults.headers.common['X-CSRF-TOKEN'] = csrf_token;

            // Perform a dummy GET request to the site root to obtain the larevel_token cookie
            // which is used for authentication. Strangely enough this cookie is not set with the
            // POST request to the login function.
            axios.get('/')
                .then( () => {
                    this.authenticating = false;
                    this.$router.replace(this.$route.query.redirect || '/');
                })
                .catch(e => this.showErrorMessage(e.message));
        })
        .catch( error => {
            this.authenticating = false;
            if(error.response && [422, 423].includes(error.response.status) ){
                this.validationErrors = error.response.data.errors;
                this.showErrorMessage(error.response.data.message);
            }else{
                this.showErrorMessage(error.message);  
            }
        });

und die authenticate() Aktion in meinem Vuex Store ist wie folgt:

authenticate({ dispatch }, data){
    return new Promise( (resolve, reject) => {
        axios.post(LOGIN, data)
            .then( response => {
                const {csrf_token, ...user} = response.data;
                // Set Vuex state
                dispatch('setUser', user );
                // Store the user data in local storage
                Vue.ls.set('user', user );
                return resolve(csrf_token);
            })
            .catch( error => reject(error) );
    });
},

Da ich refreshTokens nicht zusätzlich zum Dummy-Aufruf von / aufrufen wollte, habe ich den csrf_token an die Antwort der/login-Route des Backends angehängt:

protected function authenticated(\Illuminate\Http\Request $request, $user)
{
    $user->last_login = \Carbon\Carbon::now();
    $user->timestamps = false;
    $user->save();
    $user->timestamps = true;

    return (new UserResource($user))->additional([
        'permissions' => $user->getUIPermissions(),
        'csrf_token' => csrf_token()
    ])->response();
}
2
Daniel Schreij

In Anbetracht dessen, dass Sie eine API für die Authentifizierung verwenden, würde ich vorschlagen, Passport oder JWT-Authentifizierung zu verwenden, um Authentifizierungstoken zu verarbeiten.

4
CUGreen

Sie sollten Passports CreateFreshApiToken-Middleware in Ihrer Web-Middleware verwenden Passport, der Ihre API verbraucht

web => [...,
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

dies fügt an alle Ihre Request-Header das Recht csrftoken() als request_cookies an

1
Jon Awoyele