2017-10-31 1 views
6

Ich baue eine Single-Page-App mit Vue (2.5) mit Laravel (5.5) als Backend. Alles funktioniert gut, außer dass man sich nach dem Ausloggen direkt wieder anmeldet. In diesem Fall schlägt der Aufruf von/api/user (zum Abrufen der Kontoinformationen des Benutzers und zum erneuten Überprüfen der Identität des Benutzers) mit einem nicht autorisierten 401 fehl (obwohl die Anmeldung erfolgreich war). Als Antwort wird der Benutzer direkt zum Anmeldebildschirm zurückgeworfen (ich habe diese Maßnahme selbst als Reaktion auf 401 Antworten geschrieben).Authentifizierungstoken für ein Vue.js SPA mit Laravel für das Backend aktualisieren

Wie funktioniert es, sich abzumelden, aktualisieren Sie die Seite mit Strg/Cmd + R, und melden Sie sich dann erneut an. Die Tatsache, dass eine Seitenaktualisierung mein Problem behebt, gibt mir Anlass zu der Annahme, dass ich die Aktualisierung von X-CSRF-TOKEN nicht korrekt handle oder bestimmte Cookies vergesse, die Laravel verwendet (wie beschrieben here).

Dies ist ein Codeausschnitt des Login-Formulars, das ausgeführt wird, nachdem ein Benutzer auf die Login-Schaltflä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 Login-Endpunkt an der Laravel Seite aufruft.

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

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

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

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

Danke für jede Hilfe!


EDIT am: 07 Nov 2017

Nachdem alle Anregungen, würde Ich mag einige Informationen hinzuzufügen. Ich verwende Passport zur Authentifizierung auf der Laravel-Seite, und die CreateFreshApiToken-Middleware ist vorhanden.

Ich habe mir die von meiner App gesetzten Cookies angeschaut, insbesondere die laravel_token, von der gesagt wird, dass sie den verschlüsselten JWT enthält, den Passport zur Authentifizierung von API-Anfragen aus Ihrer JavaScript-Anwendung verwendet. Beim Ausloggen wird der Laravel_Token-Cookie gelöscht. Beim erneuten Einloggen direkt danach (mit Axios zum Senden einer AJAX-Post-Anfrage) wird keine neue laravel_token eingestellt, weshalb der Benutzer nicht authentifiziert wird. Mir ist bekannt, dass Laravel den Cookie nicht auf die Anmelde-POST-Anfrage setzt, sondern die GET-Anfrage an/refreshTokens (die nicht bewacht wird) direkt danach sollte den Cookie setzen. Dies scheint jedoch nicht zu geschehen.

Ich habe versucht, die Verzögerung zwischen der Anfrage an und die Anfrage an /api/user, vielleicht geben Sie dem Server etwas Zeit, um die Dinge in Ordnung zu bringen, aber ohne Erfolg.

Der Vollständigkeit halber, hier ist meine Auth \ LoginController, dass die Anmeldeanforderung serverseitige Handhabung:

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(); 
    } 
} 
+0

Wie ist die Abmeldefunktion implementiert? – vtosh

+0

Es ruft die Abmeldefunktion von Laravel mit einer Postanforderung auf. so einfach 'axios.post ('/ logout')'; –

+0

Wenn Sie sich die Ajax-Anfragen für Abmelden/Anmelden ansehen, sehen Sie, dass ein neues Token von/refreshtokens generiert und zurückgegeben wird, und ist dies das Token, das bei den nachfolgenden (und fehlgeschlagenen) Aufrufen an den Server verwendet wird? –

Antwort

1

Schließlich fixiert befestigt befestigen!

Durch die Rückgabe der UserResource direkt in der LoginControllers authenticated Methode, es ist keine gültige Laravel Response (aber ich denke, rohe JSON-Daten?) So wahrscheinlich Dinge wie Cookies nicht beigefügt sind. Ich musste an Response() auf der Ressource einen Anruf anfügen und jetzt scheint alles gut zu funktionieren (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 
} 

Hurray für die Laravel docs auf einen Abschnitt dieses zuschreibt: https://laravel.com/docs/5.5/eloquent-resources#resource-responses

Zusätzlich wird die laravel_token nicht von der POST-Anfrage gesetzt sich anzumelden, und der Aufruf von refreshCsrfToken() hat es auch nicht geschafft, wahrscheinlich weil es von der Gast-Mitte geschützt wurde Ware.

Am Ende funktionierte es für mich, einen Dummy-Aufruf an '/' auszuführen, direkt nachdem die Login-Funktion zurückgegeben wurde (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 Speicher 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)); 
    }); 
}, 

Weil ich nicht wollte Um einen zusätzlichen Anruf an refreshTokens zusätzlich zu dem Dummy-Aufruf an / zu machen, habe ich das csrf_token an die Antwort der/Login-Route des Back-End:

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(); 
} 
3

Bedenkt man, dass Sie eine api für die Authentifizierung verwenden, würde ich vorschlagen, Passport oder JWT Authentication mit zu handhaben Authentifizierungstoken.

+0

Danke. Ich benutze Passport. Es besagt, dass es sich automatisch um die JWT-Authentifizierung gemäß https://laravel.com/docs/5.5/passport#consuming-your-api-with-javascript kümmern sollte. Dies funktioniert gut, bis auf das Abmeldeproblem. Ich habe die Idee, dass ich nicht die Cookie-Daten richtig handhabe, die mit jeder Antwort gemäß https://laravel.com/docs/5.5/csrf#csrf-x-csrf-token zurückgegeben werden. –

+0

Ah Entschuldigung war nicht offensichtlich . Ich würde über Ihre Konfigurationen und Setup gehen und stellen Sie sicher, dass alles korrekt ist – CUGreen

+0

Ich habe schon, konnte aber nichts außer Betrieb finden. Ich denke, mein Vorgehen ist korrekt, aber mir fehlt ein Teil des Puzzles (wie die Cookie-Daten). Ich bezweifle, dass ich der Erste bin, der auf dieses Problem stößt, also lasst uns hoffen, dass jemand den Fehler entdeckt. –

1

Sie sollten passport consuming-your-api in Ihrem Web-Middleware-Pässe CreateFreshApiToken Middleware verwenden

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

dies das Recht, es csrftoken() auf alle Ihre Anfrage Header als request_cookies

+0

Danke für den Vorschlag, aber das war bereits vorhanden und ich benutzte die CreateFreshApiToken Middleware –

Verwandte Themen