Single Page Application Login (Vuejs)

In a normal login progress, a solution is:

  1. Check login status, when user enters a page or when the router changes ( the value stored in cookie or local storage)
  2. If we find login status, then we can get the user information.
  3. Otherwise, redirect the user to login page
  4. In login page, validate user input.
  5. Send login request
  6. If not success, the return a error response to user.
  7. If success, get information and save the login status
  8. When a user logout, delete login information

Now, install the vue-cli first by command

1
2
3
4
5
6
7
8
# install vue-cli
$ npm install --global vue-cli
# create a new project template based on webpack
$ vue init webpack my-project
# install dependencies
$ cd my-project
$ npm install
$ npm run dev

Register router

Create router

In the src/router/index.js file, register two routers, the first one is /login and the other one is /user. And import two compnents that will be created later.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import User from '@/components/User'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/login',
name: 'login',
component: Login
},
{
path:'/user',
name:'user',
component: User
}
]
})

Check login status

We need to check the login status when: 1. User opening the application, 2. router is changing.

So create a checkLoing method in /src/main.js first

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App },
methods:{
checkLogin(){
if(!localStorage.getItem('login')){
this.$router.push('/login');
}else{
this.$router.push('/user');
}
}
}
})

And then we add this method in the created hook so that we could check the login status at the start stage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
el: '#app',
created:function(){
this.checkLogin();
}
router,
template: '<App/>',
components: { App },
methods:{
checkLogin(){
if(!localStorage.getItem('login')){
this.$router.push('/login');
}else{
this.$router.push('/user');
}
}
}
});

Ceate components

Now create three components under src/components

Loading component

Loading.vue is used to show the loading status

1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<p>Loading</p>
</div>
</template>

<script>
export default {
name: 'loading',
}
</script>

Login component

Login.vue is used to show the login form and process the login request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<template>
<div>
<form action="/post" v-if="!loading">
<label><b>Username</b></label>
<input type="text" v-model="username">
<label><b>Password</b></label>
<input type="password" v-model="password">
<input type="submit" v-on:click.prevent="login">
</form>
<Loading v-if="loading"></Loading>
</div>
</template>

<script>
import Loading from './Loading.vue'
export default {
name: 'login',
components:{Loading},
data () {
return {
loading: false,
username: "",
password: ""
}
},
methods:{
login: function(){
// should be replaced by real login code
// there I just did some simple validation and use a fake login
if(this.username != '' && this.password!= '' )
{
// show the loading message
this.loading = true;
setTimeout(()=>{
this.loading = false;

// use vuex to store user inforamtion
this.$store.dispatch('update_user_name',this.username);

// save login status in localstorage
localStorage.setItem('login', true);

// redirect to user page
this.$router.push('/user')
},1000);
}
}
}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}

ul {
list-style-type: none;
padding: 0;
}

li {
display: inline-block;
margin: 0 10px;
}

a {
color: #42b983;
}
</style>

User component

User.vue is used to display user information

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<div class="hello">
<h1>{{ greeting }}</h1>
</div>
</template>

<script>
export default {
name: 'user',
data () {
return {

}
},
computed: {
greeting(){
// get user name from vuex
return 'Welcome' + this.$store.state.username;
}
}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}

ul {
list-style-type: none;
padding: 0;
}

li {
display: inline-block;
margin: 0 10px;
}

a {
color: #42b983;
}
</style>

Use Vuex

Actually, above code doesn’t work. This is because the applicaiton hasn’t used the Vuex

How to add vuex, just run the following command in the termainal under the project root folder

1
npm install vuex --save

After that, in the src/main.js file, improt vuex

1
import Vuex from 'vuex'

Then, use Vue.use() to install vuex

1
Vue.use(Vuex);

Finally, create a state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = Vuex.Store({
state:{
username:''
},
mutations:{
username(state,name){
state.username = name;
}
},
actions:{
update_user_name(store,name){
store.commit('username',name);
}
}
})

And modify the checkLogin method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
checkLogin(){
// if not login, redirect to login page
if(!localStorage.getItem("login"))
{
this.$router.push('login');
return;
}

// if user refresh the page, information stroed in vuex will lost.
// so we need to get user information again, based on the information stored in localstorage,
// in this application, I only stored a boolean value,
// but I think in a real life applicaiton, this could be a token or id or somethig else that can be sent to server to identify a user
if(!this.$store.username && localStorage.getItem('login'))
{
// I am using a fake method.
this.$store.dispatch('update_user_name',123);
this.$router.push('user');
return;
}

// if everything is fine, redirect
if(this.$store.username && localStorage.getItem('login'))
{
this.$router.push('user');
return;
}
}

So the whole main.js looks like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import Vuex from 'vuex'
Vue.use(Vuex);

const store = new Vuex.Store({
state: {
username: ''
},
mutations: {
username (state,name) {
state.username = name;
}
},
actions: {
update_user_name(store,name)
{
store.commit('username',name);
}
}
})

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
el: '#app',
created:function(){
this.checkLogin()
},
watch:{
'$route':'checkLogin'
},
router,store,
template: '<App/>',
components: { App },
methods:{
checkLogin(){

if(!localStorage.getItem("login"))
{
this.$router.push('login');
return;
}

if(!this.$store.username && localStorage.getItem('login'))
{
this.$store.dispatch('update_user_name',123);
this.$router.push('user');
return;
}

if(this.$store.username && localStorage.getItem('login'))
{
this.$router.push('user');
return;
}
}
}
})

via GIPHY

Source code is here