{"id":2134,"date":"2022-04-29T16:09:12","date_gmt":"2022-04-29T09:09:12","guid":{"rendered":"https:\/\/wptips.dev\/?p=2134"},"modified":"2022-11-19T13:10:18","modified_gmt":"2022-11-19T06:10:18","slug":"wp-login-with-vue","status":"publish","type":"post","link":"https:\/\/pixelstudio.id\/blog\/wp-login-with-vue\/","title":{"rendered":"WordPress Login with Vue JS (Authentication API)"},"content":{"rendered":"\n<p class=\"has-light-yellow-background-color has-background\"><strong>Update Nov 2022<\/strong>: This tutorial uses Vue 2. If you prefer Vue 3, check out <a href=\"https:\/\/github.com\/hrsetyono\/wp-vue-boilerplate\" data-type=\"URL\" data-id=\"https:\/\/github.com\/hrsetyono\/wp-vue-boilerplate\">my WP Vue Boilerplate<\/a> that comes with Registration and ForgotPassword. <\/p>\n\n\n\n<p><strong>Headless WordPress<\/strong> is more powerful than most people thought. The API is easy to customize and most people are already familiar with the admin UI.<\/p>\n\n\n\n<p>One thing I had trouble with is integrating Login authentication with Vue. So I will share what I have learned here:<\/p>\n\n\n\n<p><strong>TABLE OF CONTENT<\/strong><\/p>\n\n\n\n<ol><li><a href=\"#vue-installation\" data-type=\"internal\" data-id=\"#vue-installation\">Vue Installation<\/a><\/li><li><a href=\"#wp-installation\" data-type=\"internal\" data-id=\"#wp-installation\">WP Installation<\/a><\/li><li><a href=\"#project-setup\">Project Setup<\/a><\/li><li><a href=\"#router\">Router<\/a><\/li><li><a href=\"#login-ui\" data-type=\"internal\" data-id=\"#login-ui\">Login UI<\/a><\/li><li><a href=\"#store\">Store<\/a><\/li><li><a href=\"#env-variable\" data-type=\"URL\">Environment Variable<\/a><\/li><li><a href=\"#finishing\">Finishing<\/a><\/li><li><a href=\"#try\">Try it Out<\/a><\/li><\/ol>\n\n\n\n<p class=\"has-light-yellow-background-color has-background\"><strong>TLDR<\/strong>: Read the <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/hrsetyono\/wp-vue-boilerplate\/tree\/blog-2134\" data-type=\"URL\" data-id=\"https:\/\/github.com\/hrsetyono\/wp-vue-boilerplate\/tree\/blog-2134\" target=\"_blank\">finished code<\/a> from my Github.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"vue-installation\">1. Vue Installation<\/h2>\n\n\n\n<p>Let&#8217;s start by generating the boilerplate files. If you don&#8217;t have Vue CLI installed, run this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">npm install -g @vue\/cli<\/code><\/pre>\n\n\n\n<p>Then run this command to create a new project in &#8220;\/my-app&#8221; folder.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">vue create my-app<\/code><\/pre>\n\n\n\n<p>This tutorial uses <strong>Vue 2<\/strong>. But feel free to use Vue 3 if you know which part to change:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"583\" height=\"315\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-generate.jpg\" alt=\"\" class=\"wp-image-2148\" srcset=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-generate.jpg 583w, https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-generate-480x259.jpg 480w\" sizes=\"auto, (max-width: 583px) 100vw, 583px\" \/><\/figure>\n\n\n\n<p>After the boilerplate is generated, modify <code>package.json<\/code> to add axios, vue-router, vuex, node-sass, and sass-loader:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">{\n  \"name\": \"my-app\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^0.21.1\",\n    \"core-js\": \"^3.6.5\",\n    \"vue\": \"^2.6.11\",\n    \"vue-router\": \"^3.5.2\",\n    \"vuex\": \"^3.4.0\"\n  },\n  \"devDependencies\": {\n    \"@vue\/cli-plugin-babel\": \"~4.5.13\",\n    \"@vue\/cli-plugin-eslint\": \"~4.5.13\",\n    \"@vue\/cli-service\": \"~4.5.13\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-plugin-vue\": \"^6.2.2\",\n    \"vue-template-compiler\": \"^2.6.11\",\n    \"node-sass\": \"^4.12.0\",\n    \"sass-loader\": \"^8.0.2\"\n  },\n  \"babel\": {\n    \"presets\": [\n      \"@vue\/cli-plugin-babel\/preset\"\n    ]\n  },\n  \"eslintConfig\": {\n    \"root\": true,\n    \"env\": {\n      \"node\": true\n    },\n    \"extends\": [\n      \"plugin:vue\/essential\",\n      \"eslint:recommended\"\n    ],\n    \"parserOptions\": {\n      \"parser\": \"babel-eslint\"\n    },\n    \"rules\": {}\n  },\n  \"browserslist\": [\n    \"&gt; 1%\",\n    \"last 2 versions\",\n    \"not dead\"\n  ]\n}\n<\/code><\/pre>\n\n\n\n<p>Finally, run this command to install all packages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">npm install<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"wp-installation\">2. WP Installation<\/h2>\n\n\n\n<p class=\"has-light-yellow-background-color has-background\">I assume you have an existing WordPress installation, either a local or live one. If you don&#8217;t, create <a rel=\"noreferrer noopener\" href=\"https:\/\/pixelstudio.id\/blog\/local-flywheel-review\/\" data-type=\"URL\" data-id=\"https:\/\/pixelstudio.id\/blog\/local-flywheel-review\/\" target=\"_blank\">localhost with LOCAL<\/a>.<\/p>\n\n\n\n<p><strong>Install this plugin<\/strong> in your WordPress: <a rel=\"noreferrer noopener\" href=\"https:\/\/wordpress.org\/plugins\/jwt-authentication-for-wp-rest-api\/\" data-type=\"URL\" data-id=\"https:\/\/wordpress.org\/plugins\/jwt-authentication-for-wp-rest-api\/\" target=\"_blank\">JWT Authentication<\/a>.<\/p>\n\n\n\n<p>Then add this to your wp-config:<\/p>\n\n\n\n<pre title=\"wp-config.php\" class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">define('JWT_AUTH_CORS_ENABLE', true);\ndefine('JWT_AUTH_SECRET_KEY', 'insert-random-hash');<\/code><\/pre>\n\n\n\n<p>You can get the random hash by picking any from <a rel=\"noreferrer noopener\" href=\"https:\/\/api.wordpress.org\/secret-key\/1.1\/salt\/\" target=\"_blank\">https:\/\/api.wordpress.org\/secret-key\/1.1\/salt\/<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"project-setup\">3. Project Setup<\/h2>\n\n\n\n<p>We will create these 7 new files:<\/p>\n\n\n\n<ul><li><code>src\/router.js<\/code> &#8211; handles URL routing and permission.<\/li><li><code>src\/UserLogin.vue<\/code> &#8211; Login page.<\/li><li><code>src\/components\/Loading.vue<\/code> &#8211; Loading animation.<\/li><li><code>src\/store.js<\/code> &#8211; handles global state like &#8220;isLoggedIn&#8221; or &#8220;isAdmin&#8221;.<\/li><li><code>src\/Home.vue<\/code> &#8211; Plain homepage just to see if we successfully logged in.<\/li><li><code>.env.development<\/code> and <code>.env.production<\/code> &#8211; Environment variable.<\/li><\/ul>\n\n\n\n<p>Now, your project structure looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">my-app\/\n\u251c\u2500\u2500 public\/...\n\u251c\u2500\u2500 src\/\n\u2502   \u251c\u2500\u2500 assets\/...\n\u2502   \u251c\u2500\u2500 components\/\n\u2502   \u2502   \u251c\u2500\u2500 Loading.vue\n\u2502   \u251c\u2500\u2500 App.vue\n\u2502   \u251c\u2500\u2500 Home.vue\n\u2502   \u251c\u2500\u2500 main.js\n\u2502   \u251c\u2500\u2500 router.js\n\u2502   \u251c\u2500\u2500 store.js\n\u2502   \u2514\u2500\u2500 UserLogin.vue\n\u251c\u2500\u2500 .env.development\n\u251c\u2500\u2500 .env.production\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 README.md\n\u2514\u2500\u2500 package.json\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"router\">4. Router<\/h2>\n\n\n\n<p>There will be two primary functions of <code>router.js<\/code>:<\/p>\n\n\n\n<ul><li><strong>Manage URL<\/strong> &#8211; going to \/login will render <code>UserLogin.vue<\/code>.<\/li><li><strong>Manage Permission<\/strong> &#8211; If we&#8217;re not logged in, we can&#8217;t access Home.<\/li><\/ul>\n\n\n\n<p>This is the code: (all of the important parts are commented on)<\/p>\n\n\n\n<pre title=\"src\/router.js\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">import Vue from 'vue';\nimport VueRouter from 'vue-router';\nimport store from '.\/store';\n\nimport Home from '.\/Home.vue';\nimport UserLogin from '.\/UserLogin.vue';\n\nVue.use(VueRouter);\n\nconst routes = [\n  {\n    path: '\/',\n    name: 'Home',\n    component: Home,\n    meta: {\n      title: 'Home',\n    },\n  },\n  {\n    path: '\/login',\n    name: 'UserLogin',\n    component: UserLogin,\n    meta: {\n      title: 'Login',\n      noAuthRequired: true,\n    },\n  },\n];\n\nconst router = new VueRouter({\n  mode: 'history',\n  base: '\/',\n  routes,\n  scrollBehavior() {\n    document.getElementById('app').scrollIntoView({ behavior: 'smooth' });\n  },\n});\n\n\/\/ Set SEO metatag\nrouter.beforeEach((to, from, next) =&gt; {\n  document.title = `${to.meta.title} | My App`;\n  next();\n});\n\n\/\/ Logged-in state check\nrouter.beforeEach((to, from, next) =&gt; {\n  \/\/ redirect to Login page if auth required but not logged in\n  if (!to.meta.noAuthRequired &amp;&amp; !store.state.isLoggedIn) {\n    next({\n      name: 'UserLogin',\n      query: {\n        redirectTo: to.name,\n        message: 'Please login to visit that page',\n      },\n    });\n  }\n\n  next();\n});\n\nexport default router;\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"login-ui\">5. Login UI<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"408\" height=\"441\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-try1.jpg\" alt=\"\" class=\"wp-image-2150\"\/><\/figure>\n\n\n\n<p>We will create a simple interface with submit listener.<\/p>\n\n\n\n<p>Here is the <strong>UserLogin.vue<\/strong> file:<\/p>\n\n\n\n<pre title=\"src\/UserLogin.vue\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">&lt;template&gt;\n  &lt;div class=\"form-wrapper\"&gt;\n    &lt;!-- Error message --&gt;\n    &lt;div v-if=\"message\" class=\"form-message is-error-message\"&gt;\n      {{ message }}\n    &lt;\/div&gt;\n    &lt;!-- When redirected from secured page --&gt;\n    &lt;div v-else-if=\"$route.query.message\" class=\"form-message\"&gt;\n      {{ $route.query.message }}\n    &lt;\/div&gt;\n\n    &lt;!-- Main form --&gt;\n    &lt;form class=\"form\" @submit.prevent=\"login\"&gt;\n      &lt;label&gt;\n        &lt;span&gt;Username&lt;\/span&gt;\n        &lt;input\n          v-model.trim=\"username\"\n          type=\"text\"\n          placeholder=\"username or email\"\n        &gt;\n      &lt;\/label&gt;\n      &lt;label&gt;\n        &lt;span&gt;Password&lt;\/span&gt;\n        &lt;input\n          v-model.trim=\"password\"\n          type=\"password\"\n        &gt;\n      &lt;\/label&gt;\n      &lt;button type=\"submit\"&gt;\n        Log In\n      &lt;\/button&gt;\n    &lt;\/form&gt;\n\n    &lt;Loading v-if=\"isLoading\" \/&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n&lt;script&gt;\nimport Loading from '.\/components\/Loading.vue';\n\nexport default {\n  name: 'UserLogin',\n  components: {\n    Loading,\n  },\n  data() {\n    return {\n      username: '',\n      password: '',\n\n      message: '',\n      isLoading: false,\n    };\n  },\n\n  methods: {\n    async login() {\n      this.message = '';\n      this.isLoading = true;\n\n      try {\n        \/\/ Call the login function in store.js\n        await this.$store.dispatch('login', {\n          username: this.username,\n          password: this.password,\n        });\n\n        this.isLoading = true;\n\n        \/\/ if redirected to login from secured page, redirect back\n        if (this.$route.query.redirectTo) {\n          this.$router.push({ name: this.$route.query.redirectTo });\n        }\n        \/\/ else redirect to Home\n        else {\n          this.$router.push({ name: 'Home' });\n        }\n      } catch (error) {\n        this.isLoading = false;\n        this.message = 'Email or password is wrong';\n      }\n    }\n  }\n}\n&lt;\/script&gt;\n\n&lt;style lang=\"sass\"&gt;\n.form-wrapper\n  max-width: 360px\n  margin: 3rem auto\n\n.form-message\n  padding: 0.25rem\n  margin-bottom: 1rem\n  box-shadow: 0 0 10px 0 rgba(black, .1)\n  \n  &amp;.is-error-message\n    color: red\n\n.form\n  padding: 1rem\n  border: 1px solid gray\n\n  label\n    display: block\n    margin-bottom: 1rem\n\n  input\n    width: 100%\n    padding: 0.5rem\n\n  span\n    display: block\n    text-transform: uppercase\n    font-size: smaller\n\n  button\n    padding: 0.5rem 1rem\n    background-color: green\n    color: white\n&lt;\/style&gt;<\/code><\/pre>\n\n\n\n<p>Notice the <code>$store.dispatch()<\/code> in the login listener? This is calling the function  inside <code>store.js<\/code>. We will implement that later.<\/p>\n\n\n\n<p>We also added Loading animation by importing <strong>Loading.vue<\/strong>. Here&#8217;s the code:<\/p>\n\n\n\n<pre title=\"src\/components\/Loading.vue\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">&lt;template&gt;\n  &lt;div class=\"loading\"&gt;\n    &lt;span \/&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n&lt;script&gt;\nexport default {\n  name: 'Loading',\n};\n&lt;\/script&gt;\n\n&lt;style lang=\"sass\" scoped&gt;\n.loading\n  top: 0\n  left: 0\n  z-index: 101\n  height: 100%\n  width: 100%\n  background-color: rgba(black, .5)\n  position: fixed\n  display: flex\n  justify-content: center\n  align-items: center\n\n  @keyframes spin\n    to\n      transform: rotateZ(360deg)\n\n  span\n    display: block\n    width: 60px\n    height: 60px\n    margin: 0 auto\n    border: 3px solid transparent\n    border-top-color: #fff\n    border-bottom-color: #fff\n    border-radius: 50%\n    animation: spin ease 1000ms infinite\n&lt;\/style&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"store\">6. Store<\/h2>\n\n\n\n<p>Store is a Vuex feature to manage global variables and functions. It consisted of three parts:<\/p>\n\n\n\n<ul><li><strong>state<\/strong> &#8211; The global variables.<\/li><li><strong>mutations<\/strong> &#8211; Functions to modify the global variables.<\/li><li><strong>actions<\/strong> &#8211; The global functions.<\/li><\/ul>\n\n\n\n<p>Here&#8217;s our <code>store.js<\/code> file:<\/p>\n\n\n\n<pre title=\"src\/store.js\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">import Vue from 'vue';\nimport Vuex from 'vuex';\nimport axios from 'axios';\n\nVue.use(Vuex);\n\nconst mutations = {\n  cacheUser(state, { token, email, displayName }) {\n    state.isLoggedIn = true;\n    state.userToken = token;\n    state.userEmail = email;\n    state.userDisplayName = displayName;\n  },\n\n  deleteUserCache(state) {\n    state.isLoggedIn = false;\n    state.userToken = '';\n    state.userEmail = '';\n    state.userDisplayName = '';\n  },\n};\n\nconst actions = {\n  async login({ commit }, payload) {\n    const response = await axios.post(`${process.env.VUE_APP_API_URL}\/jwt-auth\/v1\/token`, {\n      username: payload.username,\n      password: payload.password,\n    });\n\n    const data = response.data;\n\n    localStorage.setItem('isLoggedIn', true);\n    localStorage.setItem('token', data.token);\n    localStorage.setItem('displayName', data.user_display_name);\n    localStorage.setItem('email', data.user_email);\n\n    \/\/ call cacheUser() from mutations\n    await commit('cacheUser', {\n      token: data.token,\n      email: data.user_email,\n      displayName: data.user_display_name,\n    });\n  },\n\n  async logout({ commit }) {\n    localStorage.removeItem('isLoggedIn');\n    localStorage.removeItem('token');\n    localStorage.removeItem('email');\n    localStorage.removeItem('displayName');\n\n    commit('deleteUserCache');\n  },\n\n  \/**\n   * Check if user if logged in\n   *\/\n  async checkLoginState({ commit }) {\n    const token = localStorage.getItem('token');\n\n    \/\/ if no token, empty the loggedIn cache\n    if (!token) {\n      await commit('deleteUserCache');\n      return false;\n    }\n\n    \/\/ if has token, check if it's still valid\n    try {\n      await axios.post(\n        `${process.env.VUE_APP_API_URL}\/jwt-auth\/v1\/token\/validate`,\n        {},\n        {\n          headers: {\n            Authorization: `Bearer ${token}`,\n          },\n        },\n      );\n      \n      \/\/ if still valid, cache it again\n      await commit('cacheUser', {\n        token,\n        email: localStorage.getItem('email'),\n        displayName: localStorage.getItem('displayName'),\n      });\n\n      return true;\n    } catch (error) {\n      localStorage.setItem('token', '');\n      return false;\n    }\n  },\n}\n\n\nconst store = {\n  \/\/ This is global data, use mutations and actions to change this value.\n  state: {\n    isAdmin: false,\n    isLoggedIn: localStorage.getItem('isLoggedIn') === 'true',\n    userToken: localStorage.getItem('token') || '',\n    userEmail: localStorage.getItem('email') || '',\n    userDisplayName: localStorage.getItem('displayName') || '',\n  },\n\n  mutations,\n  actions,\n  modules: {\n  },\n};\n\nexport default new Vuex.Store(store);\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"env-variable\">7. Environment Variable<\/h2>\n\n\n\n<p>Check out the <code>login()<\/code> function above. The API call is using environment variable for the domain name.<\/p>\n\n\n\n<p>You set this in <code>.env.development<\/code> and <code>.env.production<\/code>:<\/p>\n\n\n\n<pre title=\".env.development\" class=\"wp-block-code\"><code lang=\"txt\" class=\"language-txt\">VUE_APP_API_URL=http:\/\/mysite.test\/wp-json<\/code><\/pre>\n\n\n\n<pre title=\".env.production\" class=\"wp-block-code\"><code lang=\"txt\" class=\"language-txt\">VUE_APP_API_URL=http:\/\/mysite.com\/wp-json<\/code><\/pre>\n\n\n\n<p class=\"has-light-yellow-background-color has-background\"><strong>Note<\/strong>: The variable name has to be prefixed with VUE_APP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"finishing\">8. Finishing<\/h2>\n\n\n\n<p>We are left with 3 more files to customize:<\/p>\n\n\n\n<ul><li><code>Home.vue<\/code> &#8211; App dashboard. But for this tutorial, we only put sample text.<\/li><li><code>App.vue<\/code> &#8211; Contains site layout like Header and Footer.<\/li><li><code>main.js<\/code> &#8211; Entry point<\/li><\/ul>\n\n\n\n<p>Here&#8217;s <strong>Home.vue<\/strong>: (nothing special, just plain text)<\/p>\n\n\n\n<pre title=\"src\/Home.vue\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">&lt;template&gt;\n  &lt;div class=\"page\"&gt;\n    &lt;h1&gt;Hello World&lt;\/h1&gt;\n    &lt;p&gt;\n      Lorem, ipsum dolor sit amet consectetur adipisicing elit. Corporis nisi aliquid sunt culpa quia dolores porro repellat a magni aliquam, quo consectetur vero labore minus accusantium ducimus!\n    &lt;\/p&gt;\n    &lt;p&gt;\n      Ratione consectetur voluptatibus dolorum, facilis rerum maxime architecto magni? Corporis fuga necessitatibus qui mollitia! Nesciunt eligendi doloribus eum quos sapiente officia ex architecto?\n    &lt;\/p&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n&lt;script&gt;\nexport default {\n  name: 'Home',\n  data() {\n    return {};\n  },\n}\n&lt;\/script&gt;\n\n&lt;style lang=\"sass\"&gt;\n.page\n  max-width: 1000px\n  margin: 2rem auto\n&lt;\/style&gt;<\/code><\/pre>\n\n\n\n<p><strong>App.vue<\/strong> contains Header and Footer. The header has a <strong>logout <\/strong>link, so we will implement that:<\/p>\n\n\n\n<pre title=\"src\/App.vue\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">&lt;template&gt;\n  &lt;div id=\"app\"&gt;\n    &lt;header class=\"main-header\"&gt;\n      &lt;router-link :to=\"{ name: 'Home' }\"&gt;\n        &lt;img alt=\"Logo\" src=\".\/assets\/logo.png\"&gt;\n      &lt;\/router-link&gt;\n\n      &lt;nav v-if=\"$store.state.isLoggedIn\"&gt;\n        &lt;!-- Show menu navigation --&gt;\n\n        &lt;a href=\"#logout\" @click.prevent=\"logout\"&gt;\n          Logout\n        &lt;\/a&gt;\n      &lt;\/nav&gt;\n    &lt;\/header&gt;\n\n    &lt;router-view \/&gt;\n\n    &lt;footer class=\"main-footer\"&gt;\n      \u00a9 Copyright My App - All rights reserved\n    &lt;\/footer&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n\n&lt;script&gt;\nexport default {\n  name: 'App',\n  data() {\n    return {};\n  },\n  \/\/ check if auth token is still valid\n  async created() {\n    \/\/ only check valid if coming from pages that require auth\n    if (this.$route.meta.noAuthRequired) {\n      return;\n    }\n\n    const isValid = await this.$store.dispatch('checkLoginState');\n\n    if (!isValid) {\n      this.$router.push({\n        name: 'UserLogin',\n        query: {\n          redirectTo: this.$route.name,\n        },\n      });\n    }\n  },\n\n  methods: {\n    async logout() {\n      await this.$store.dispatch('logout');\n      this.$router.push({ name: 'UserLogin' });\n    }\n  }\n}\n&lt;\/script&gt;\n\n&lt;style lang=\"sass\"&gt;\n*,\n*::before,\n*::after\n  box-sizing: border-box\n\n#app\n  font-family: Avenir, Helvetica, Arial, sans-serif\n  -webkit-font-smoothing: antialiased\n  -moz-osx-font-smoothing: grayscale\n  color: #2c3e50\n  margin-top: 60px\n\n.main-header\n  display: flex\n  justify-content: center\n  max-width: 1000px\n  margin: 0 auto 2rem\n  padding: 0.25rem 0\n  border-bottom: 1px solid\n\n  img\n    max-height: 60px\n\n  nav\n    margin-left: auto\n  \n.main-footer\n  max-width: 1000px\n  margin: 3rem auto 0\n  padding: 0.25rem 0\n  border-top: 1px solid\n  text-align: center\n\n  font-size: smaller\n&lt;\/style&gt;\n<\/code><\/pre>\n\n\n\n<p>Finally, <strong>main.js<\/strong> is your entry point. We need to import our router and store implementation here:<\/p>\n\n\n\n<pre title=\"src\/main.js\" class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">import Vue from 'vue';\nimport App from '.\/App.vue';\nimport router from '.\/router';\nimport store from '.\/store';\n\nVue.config.productionTip = false;\n\nlet app;\nif (!app) {\n  new Vue({\n    router,\n    store,\n    render: (h) =&gt; h(App),\n  }).$mount('#app');\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"try\">9. Try it Out<\/h2>\n\n\n\n<p>Make sure the JWT plugin is activated, constants in wp-config are set, and the environment variable in your Vue app is correct.<\/p>\n\n\n\n<p>Then run this command to launch the Vue server:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">npm run dev<\/code><\/pre>\n\n\n\n<p>Open the localhost URL as shown in the command result:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"583\" height=\"231\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-run.jpg\" alt=\"\" class=\"wp-image-2149\" srcset=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-run.jpg 583w, https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-run-480x190.jpg 480w\" sizes=\"auto, (max-width: 583px) 100vw, 583px\" \/><\/figure>\n\n\n\n<p>Since you&#8217;re not logged in, you will be redirected to the Login page:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"408\" height=\"441\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-try1.jpg\" alt=\"\" class=\"wp-image-2150\"\/><\/figure>\n\n\n\n<p>After logging in, you will be redirected back to Home:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"280\" src=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-try2.jpg\" alt=\"\" class=\"wp-image-2151\" srcset=\"https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-try2.jpg 750w, https:\/\/pixelstudio.id\/blog\/wp-content\/uploads\/2022\/04\/vue-login-try2-480x179.jpg 480w\" sizes=\"auto, (max-width: 750px) 100vw, 750px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>I was planning to add <strong>Register and Forgot Password<\/strong> functions. But the tutorial is already very long, so I will keep it for the future.<\/p>\n\n\n\n<p>Also if the tutorial above isn&#8217;t working, you can cross-check it with our finished code on Github. We might miss something while writing the blog.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-dark-gray-color has-yellow-background-color has-text-color has-background\" href=\"https:\/\/github.com\/hrsetyono\/wp-vue-boilerplate\/tree\/blog-2134\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Get finished code<\/strong><\/a><\/div>\n<\/div>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Thank you for reading, feel free to ask any question in the comment below.<\/p><\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Headless WordPress is more powerful than most people thought. But integrating WP Login Authentication with Vue is quite tricky. Learn more in this tutorial.<\/p>\n","protected":false},"author":1,"featured_media":2147,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12],"tags":[32,49],"class_list":["post-2134","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-frontend","tag-javascript","tag-vue"],"blocksy_meta":"","_links":{"self":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts\/2134","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/comments?post=2134"}],"version-history":[{"count":19,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts\/2134\/revisions"}],"predecessor-version":[{"id":2232,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/posts\/2134\/revisions\/2232"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/media\/2147"}],"wp:attachment":[{"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/media?parent=2134"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/categories?post=2134"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pixelstudio.id\/blog\/wp-json\/wp\/v2\/tags?post=2134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}