websanova / vue-auth

A simple light-weight authentication library for Vue.js
MIT License
2.36k stars 380 forks source link

Auth lost session when page refresh (F5 || cmd+r) #447

Closed pedroscursel closed 4 years ago

pedroscursel commented 6 years ago

Hello, I love how it works fine in vue-admin but when I start from scratch, using VUE UI it's won't work as well...

My /users endpoint (Laravel Lumen micro service):

screen shot 2018-10-25 at 08 58 28

My Vue Code:

package.json

{
  "name": "frotas",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "test:unit": "vue-cli-service test:unit"
  },
  "dependencies": {
    "@babel/plugin-proposal-export-default-from": "^7.0.0",
    "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
    "@websanova/vue-auth": "^2.21.14-beta",
    "axios": "^0.18.0",
    "bootstrap-vue": "^2.0.0-rc.11",
    "register-service-worker": "^1.0.0",
    "vee-validate": "^2.1.0-beta.11",
    "vue": "^2.5.17",
    "vue-axios": "^2.1.4",
    "vue-nprogress": "^0.1.5",
    "vue-router": "^2.8.1",
    "vue2-animate": "^2.1.0",
    "vuex": "^3.0.1",
    "vuex-router-sync": "^5.0.0"
  },
  "devDependencies": {
    "@fortawesome/fontawesome-svg-core": "^1.2.6",
    "@fortawesome/free-solid-svg-icons": "^5.4.1",
    "@fortawesome/vue-fontawesome": "^0.1.1",
    "@vue/cli-plugin-babel": "^3.0.3",
    "@vue/cli-plugin-eslint": "^3.0.3",
    "@vue/cli-plugin-pwa": "^3.0.3",
    "@vue/cli-plugin-unit-jest": "^3.0.3",
    "@vue/cli-service": "^3.0.3",
    "@vue/eslint-config-standard": "^3.0.5",
    "@vue/test-utils": "^1.0.0-beta.20",
    "autoprefixer": "^9.2.0",
    "babel-core": "7.0.0-bridge.0",
    "babel-jest": "^23.0.1",
    "bootstrap": "^4.1.3",
    "css-loader": "^1.0.0",
    "node-sass": "^4.9.0",
    "package": "^1.0.1",
    "sass-loader": "^7.0.1",
    "sass-rem": "^2.0.1",
    "style-loader": "^0.23.1",
    "vue-strap": "^1.1.40",
    "vue-template-compiler": "^2.5.17"
  }
}

router.js

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

export default new Router({
  mode: 'history',
  linkActiveClass: 'active',
  routes: [
    {
      path: '*',
      redirect: '/'
    },
    // HOME PAGE
    {
      name: 'Home',
      path: '/',
      component: () => import('./views/Home')
    },
    // LOGIN PAGE
    {
      name: 'Login',
      path: '/login',
      component: () => import('./views/Login'),
      meta: {
        auth: false
      }
    },
    // WELCOME LOGGED
    {
      name: 'Welcome',
      path: '/welcome',
      meta: {
        auth: true
      },
      component: () => import('./views/Welcome')
    }
  ]
})

main.js

import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import VueAuth from '@websanova/vue-auth'
import NProgress from 'vue-nprogress'
import { sync } from 'vuex-router-sync'
import VeeValidate from 'vee-validate'
import router from './router'
import store from './store'

import './registerServiceWorker'

import App from './App.vue'
import apiendpoint from './apiendpoint'

// BOOTSTRAPVUE
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)

Vue.router = router
Vue.use(VueAxios, axios)
Vue.use(VueAuth, {
  auth: {
    request: function (req, token) {
      this.options.http._setHeaders.call(this, req, {
        Authorization: 'Bearer ' + token
      })
    },
    response: function (res) {
      return res.data.api_key
    }
  },
  http: require('@websanova/vue-auth/drivers/http/axios.1.x.js'),
  router: require('@websanova/vue-auth/drivers/router/vue-router.2.x.js'),
  loginData: {
    // http://url_to_login_microservice.com.br/users
    url: apiendpoint.LOGIN_URL,
    fechUser: false
  },
  refreshData: { enabled: false }
})

Vue.use(VeeValidate)
Vue.use(NProgress)

sync(store, router)

Vue.config.productionTip = false
Vue.config.devtools = true

const nprogress = new NProgress({
  parent: '.nprogress-container'
})

new Vue({
  router,
  store,
  nprogress,
  render: h => h(App)
}).$mount('#app')

App.vue

<template>
  <div id="app">
    <nprogress-container></nprogress-container>
    <template v-if="this.$auth.check()">
      <navbar></navbar>
      <sidebar></sidebar>
    </template>
    <app-main></app-main>
  </div>
</template>

<script>
import NprogressContainer from 'vue-nprogress/src/NprogressContainer'
import { Sidebar, Navbar, AppMain } from './components/layout'

export default {
  components: {
    Sidebar,
    Navbar,
    AppMain,
    NprogressContainer
  }
}
</script>

<style lang="scss">
$animationDuration: 0.5s;
@import '~vue2-animate/src/sass/vue2-animate';
@import url('https://fonts.googleapis.com/css?family=Roboto');
@import '~bootstrap/scss/bootstrap';
@import '~bootstrap-vue/dist/bootstrap-vue.css';
body{
  margin: 0;
  padding: 0;
}
.nprogress-container{
  position: fixed !important;
  width: 100%;
  height: 50px;
  z-index: 2048;
  pointer-events: none;
  #nprogress{
    $color: purple;
    .bar{
      background-color: $color;
    }
    .peg{
      box-shadow: 0 0 10px $color, 0 0 5px $color;
    }
    .spinner-icon{
      border-top-color: $color;
      border-left-color: $color;
    }
  }
}
@mixin calc($property, $expression) {
  #{$property}: calc(#{$expression});
}
</style>

The Login File

I'm using vue-bootstrap markup on Login.vue file. It has some customisations for callbacks. I'm need to translate it because I'm Brazilian, but in general, it's works very well

views/Login.vue

<template>
  <section id="login">
    <b-container>
      <b-row>
        <b-col sm="12">
          <h1 class="text-center">Login</h1>
        </b-col>
        <b-col sm="9" md="6" class="ml-auto mr-auto">
          <b-form v-on:submit.prevent="login">
            <b-form-group
              label="Usuário:"
              label-for="user">
              <b-form-input id="user"
                type="text"
                v-model="data.body.username"></b-form-input>
              <transition
                name="bounce"
                enter-active-class="bounceIn"
                leave-active-class="bounceOut">
                <small v-if="userNameErr" class="form-text text-danger"> {{ callBackMsg }} </small>
              </transition>
            </b-form-group>

            <b-form-group
              label="Senha:"
              label-for="pswd"
              class="mt-4 pt-1">
              <b-form-input id="pswd"
                type="password"
                v-model="data.body.password"></b-form-input>
              <transition
                name="bounce"
                enter-active-class="bounceIn"
                leave-active-class="bounceOut"
              >
                <small v-if="pswdNameErr" class="form-text text-danger"> {{ callBackMsg }} </small>
              </transition>
            </b-form-group>
            <b-form-group
              class="mt-4 pt-1 d-flex align-items-center">
              <b-form-checkbox id="rememberMe"
                type="checkbox"
                v-model="data.rememberMe"
                class="ml-2">Lembrar</b-form-checkbox>
            </b-form-group>
            <b-form-group
              class="text-center">
              <b-button
                type="submit"
                variant="primary"
                class="mr-1">Entrar</b-button>
              <router-link
                to="Home"
                class="btn btn-secondary ml-1">Home</router-link>
            </b-form-group>
          </b-form>
        </b-col>
      </b-row>
      <transition
          name="bounce"
          enter-active-class="bounceIn"
          leave-active-class="bounceOut"
        >
        <b-row class="mt-4 pt-1" v-if="error">
          <b-col sm="9" md="6" class="ml-auto mr-auto">
            <div class="alert" :class="errorClass" role="alert">
              <p class="messages text-center mb-0">{{ callBackMsg }}</p>
            </div>
          </b-col>
        </b-row>
      </transition>
    </b-container>
  </section>
</template>

<script>
export default {
  data () {
    return {
      data: {
        body: {
          username: null,
          password: null
        },
        rememberMe: false
      },
      error: false,
      errorClass: null,
      userNameErr: false,
      pswdNameErr: false,
      callBackMsg: null
    }
  },

  mounted () {
    if (this.$auth.redirect()) {
      this.error = true
      this.errorClass = 'alert-warning'
      this.callBackMsg = 'Você veio redirecionado de: ' + this.$auth.redirect().from.name + ', para acessar este local, antes faça login!'
    }
  },

  methods: {
    whatError (isName) {
      isName ? this.userNameErr = true : this.pswdNameErr = true
      let console = isName ? this.callBackMsg = 'Preencha o campo de usuário' : this.callBackMsg = 'Preencha o campo de senha'
      return console
    },

    translateMsg (msg) {
      if (msg === 'fail') {
        return 'Senha inválida!'
      } else if (msg === 'Username not found') {
        return 'Usuário não encontrado...'
      }
    },

    areYouSure (maybe) {
      if (maybe) {
        return true
      }
    },

    login () {
      this.error = false
      this.userNameErr = false
      this.pswdNameErr = false
      let redirect = this.$auth.redirect()
      this.$auth.login({
        data: this.data.body,
        rememberMe: this.areYouSure(this.data.rememberMe),
        redirect: {
          name: redirect ? redirect.from.name : 'Welcome'
        },
        fetchUser: false,
        success (res) {
          console.log('success ' + this.context)
          console.log('Token: ' + JSON.stringify(this.$auth.token()))
        },
        error (err) {
          let errStatus = err.response.status
          let errMessage = err.response.data.status

          if (errStatus === 422) {
            if (err.response.data.username && err.response.data.password) {
              this.error = true
              this.errorClass = 'alert-danger'
              this.callBackMsg = 'Preencha os campos de usuário e senha!'
            } else {
              this.whatError(err.response.data.username)
            }
          } else if (errStatus === 401) {
            this.error = true
            this.errorClass = 'alert-danger'
            this.callBackMsg = this.translateMsg(errMessage)
          } else if (errStatus === 500) {
            this.error = true
            this.errorClass = 'alert-info'
            this.callBackMsg = 'Erro no servidor, favor contatar a equipe de desenvolvimento.'
          }
        }
      })
    }

  }

}
</script>

<style lang="scss" scoped>
  *{
    font-family: 'Roboto', sans-serif;
  }
  #login{
    padding-top: 10vh;
    .text-danger{
      position: absolute;
    }
  }
</style>

views/Home.vue

It's my "index" page, won't need to be logged

<template>
  <div id="home" class="container-fluid">
    <div class="homeWrap container d-flex justify-content-between align-items-center align-content-center flex-column">
      <header class="w-100 pt-5">
        <div class="col-12">
          <h1>FROTAS <small>v1.1.0</small></h1>
        </div>
      </header>
      <section class="mainContent w-100">
        <div class="col-12 text-center">
          <h2>Sistema de Gestão</h2>
          <p>Gerenciamento de frotas, condutores, multas e controle de acessos</p>
          <router-link
            to="login"
            class="btn btn-lg btn-primary">Acesso</router-link>
        </div>
      </section>
      <footer class="w-100 pb-5 text-center">
        <p>Developed by: <b><a href="http://fabrika162.com.br" target="_blank">Fabrika162</a></b></p>
      </footer>
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style lang="scss" scoped>
  @import "~bootstrap/scss/functions";
  @import "~bootstrap/scss/variables";
  @import "~bootstrap/scss/mixins";
  @import "~bootstrap/scss/root";
  @import "~bootstrap/scss/reboot";
  @import "~bootstrap/scss/type";
  @import "~bootstrap/scss/grid";
  @import "~bootstrap/scss/forms";
  @import "~bootstrap/scss/buttons";
  @import "~bootstrap/scss/input-group";
  @import "~bootstrap/scss/button-group";
  @import "~bootstrap/scss/alert";
  @import "~bootstrap/scss/utilities";
  @import url('https://fonts.googleapis.com/css?family=Ubuntu');
  *{
    font-family: 'Ubuntu', sans-serif;
  }
  #home{
    width: 100%;
    height: 100vh;
    box-shadow: inset 0 0 5rem rgba(0,0,0,.5);
    background-color: #333;
    .homeWrap{
      height: 100vh;
      header{
        h1{
          color: #f5f5f5;
          font-weight: 800;
        }
      }
      .mainContent{
        h2{
          color: #f5f5f5;
          font-weight: 700;
        }
        p{
          color: #f5f5f5;
        }
      }
      footer{
        color: #f5f5f5;
        a{
          color: #f5f5f5;
          text-decoration: none;
          transition: all 0.5s ease;
          &:hover{
            color: rgb(186, 186, 186);
          }
        }
      }
    }
  }
</style>

views/Welcome.vue

and, this is my Welcome page. It's need to be logged to be seen

<template>
  <div id="welcome">

  </div>
</template>

<script>
export default {

}
</script>

<style lang="scss" scoped>
  *{
    font-family: 'Roboto', sans-serif;
  }
  #welcome{
  }
</style>

The end of the code, here is my components files. I won't share the asses files, like navbar and sidebar just for save this Issue request size.

components/index.js

My index.js just get the components and exports in one file

export Navbar from './Navbar'
export Sidebar from './Sidebar'
export AppMain from './AppMain'

components/AppMain.vue

<template>
  <section id="contentWrap">
    <transition
      mode="out-in"
      enter-active-class="fadeIn"
      leave-active-class="fadeOut"
      appear>
      <router-view class="animated"></router-view>
    </transition>
  </section>
</template>

<script>
export default {

}
</script>

<style lang="scss">

</style>

For finish it, some prints using the application

screen shot 2018-10-25 at 08 53 08 screen shot 2018-10-25 at 08 54 16 screen shot 2018-10-25 at 08 54 57

Thanks Websanova for your amazing work! After you help-me I will create an video for Brazilians explaining how to fix this issue. Regras from Brazil, Pedro

jakobeissler commented 5 years ago

I'm having the exact same issue.

ImLuckyJr commented 5 years ago

Maybe someone else needs a possible solution. I had same issue. This issue was in login method in my AuthController.

Original code:

public function login(Request $request) {
        $frd = $request->only(['email', 'password']);
        $user = User::where('email', $frd['email'])->first();
        if ($user) {
            if (Hash::check($frd['password'], $user->password)) {
                $token = $user->createToken('Laravel Password Grant Client')->accessToken;
                $response = ['token' => $token];
                return response($response, 200);
            } else {
                $response = "Password missmatch";
                return response($response, 422);
            }
        } else {
            $response = 'User does not exist';
            return response($response, 422);
        }
    }

Corrected code:

...
       $token = $user->createToken('Laravel Password Grant Client')->accessToken;
       $response = ['token' => $token];
       return response($response, 200)->header('Authorization', $token);
...
websanova commented 4 years ago

Closing due to inactivity.