├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
├── _helpers
│ ├── auth-header.js
│ ├── fake-backend.js
│ ├── index.js
│ └── router.js
├── _services
│ ├── index.js
│ └── user.service.js
├── _store
│ ├── alert.module.js
│ ├── authentication.module.js
│ ├── index.js
│ └── users.module.js
├── app
│ └── App.vue
├── home
│ └── HomePage.vue
├── index.html
├── index.js
└── login
│ └── LoginPage.vue
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "stage-0"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 | typings
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jason Watmore
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-vuex-jwt-authentication-example
2 |
3 | Vue + Vuex - JWT Authentication Tutorial & Example
4 |
5 | To see a demo and further details go to http://jasonwatmore.com/post/2018/07/06/vue-vuex-jwt-authentication-tutorial-example
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-vuex-jwt-authentication-example",
3 | "version": "1.0.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/cornflourblue/vue-vuex-jwt-authentication-example.git"
7 | },
8 | "license": "MIT",
9 | "scripts": {
10 | "start": "webpack-dev-server --open"
11 | },
12 | "dependencies": {
13 | "vue": "^2.5.16",
14 | "vue-router": "^3.0.1",
15 | "vuex": "^3.0.1"
16 | },
17 | "devDependencies": {
18 | "babel-core": "^6.26.0",
19 | "babel-loader": "^7.1.5",
20 | "babel-preset-env": "^1.6.1",
21 | "babel-preset-stage-0": "^6.24.1",
22 | "babel-preset-vue": "^2.0.2",
23 | "css-loader": "^3.4.2",
24 | "html-webpack-plugin": "^3.2.0",
25 | "path": "^0.12.7",
26 | "vue-loader": "^14.2.3",
27 | "vue-template-compiler": "^2.5.16",
28 | "webpack": "^4.15.0",
29 | "webpack-cli": "^3.0.8",
30 | "webpack-dev-server": "^3.1.3"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/_helpers/auth-header.js:
--------------------------------------------------------------------------------
1 | export function authHeader() {
2 | // return authorization header with jwt token
3 | let user = JSON.parse(localStorage.getItem('user'));
4 |
5 | if (user && user.token) {
6 | return { 'Authorization': 'Bearer ' + user.token };
7 | } else {
8 | return {};
9 | }
10 | }
--------------------------------------------------------------------------------
/src/_helpers/fake-backend.js:
--------------------------------------------------------------------------------
1 | export function configureFakeBackend() {
2 | let users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];
3 | let realFetch = window.fetch;
4 | window.fetch = function (url, opts) {
5 | return new Promise((resolve, reject) => {
6 | // wrap in timeout to simulate server api call
7 | setTimeout(() => {
8 |
9 | // authenticate
10 | if (url.endsWith('/users/authenticate') && opts.method === 'POST') {
11 | // get parameters from post request
12 | let params = JSON.parse(opts.body);
13 |
14 | // find if any user matches login credentials
15 | let filteredUsers = users.filter(user => {
16 | return user.username === params.username && user.password === params.password;
17 | });
18 |
19 | if (filteredUsers.length) {
20 | // if login details are valid return user details and fake jwt token
21 | let user = filteredUsers[0];
22 | let responseJson = {
23 | id: user.id,
24 | username: user.username,
25 | firstName: user.firstName,
26 | lastName: user.lastName,
27 | token: 'fake-jwt-token'
28 | };
29 | resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(responseJson)) });
30 | } else {
31 | // else return error
32 | reject('Username or password is incorrect');
33 | }
34 |
35 | return;
36 | }
37 |
38 | // get users
39 | if (url.endsWith('/users') && opts.method === 'GET') {
40 | // check for fake auth token in header and return users if valid, this security is implemented server side in a real application
41 | if (opts.headers && opts.headers.Authorization === 'Bearer fake-jwt-token') {
42 | resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(users)) });
43 | } else {
44 | // return 401 not authorised if token is null or invalid
45 | reject('Unauthorised');
46 | }
47 |
48 | return;
49 | }
50 |
51 | // pass through any requests not handled above
52 | realFetch(url, opts).then(response => resolve(response));
53 |
54 | }, 500);
55 | });
56 | }
57 | }
--------------------------------------------------------------------------------
/src/_helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './fake-backend';
2 | export * from './router';
3 | export * from './auth-header';
--------------------------------------------------------------------------------
/src/_helpers/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 |
4 | import HomePage from '../home/HomePage'
5 | import LoginPage from '../login/LoginPage'
6 |
7 | Vue.use(Router);
8 |
9 | export const router = new Router({
10 | mode: 'history',
11 | routes: [
12 | { path: '/', component: HomePage },
13 | { path: '/login', component: LoginPage },
14 |
15 | // otherwise redirect to home
16 | { path: '*', redirect: '/' }
17 | ]
18 | });
19 |
20 | router.beforeEach((to, from, next) => {
21 | // redirect to login page if not logged in and trying to access a restricted page
22 | const publicPages = ['/login'];
23 | const authRequired = !publicPages.includes(to.path);
24 | const loggedIn = localStorage.getItem('user');
25 |
26 | if (authRequired && !loggedIn) {
27 | return next('/login');
28 | }
29 |
30 | next();
31 | })
--------------------------------------------------------------------------------
/src/_services/index.js:
--------------------------------------------------------------------------------
1 | export * from './user.service';
2 |
--------------------------------------------------------------------------------
/src/_services/user.service.js:
--------------------------------------------------------------------------------
1 | import config from 'config';
2 | import { authHeader } from '../_helpers';
3 |
4 | export const userService = {
5 | login,
6 | logout,
7 | getAll
8 | };
9 |
10 | function login(username, password) {
11 | const requestOptions = {
12 | method: 'POST',
13 | headers: { 'Content-Type': 'application/json' },
14 | body: JSON.stringify({ username, password })
15 | };
16 |
17 | return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
18 | .then(handleResponse)
19 | .then(user => {
20 | // login successful if there's a jwt token in the response
21 | if (user.token) {
22 | // store user details and jwt token in local storage to keep user logged in between page refreshes
23 | localStorage.setItem('user', JSON.stringify(user));
24 | }
25 |
26 | return user;
27 | });
28 | }
29 |
30 | function logout() {
31 | // remove user from local storage to log user out
32 | localStorage.removeItem('user');
33 | }
34 |
35 | function getAll() {
36 | const requestOptions = {
37 | method: 'GET',
38 | headers: authHeader()
39 | };
40 |
41 | return fetch(`${config.apiUrl}/users`, requestOptions).then(handleResponse);
42 | }
43 |
44 | function handleResponse(response) {
45 | return response.text().then(text => {
46 | const data = text && JSON.parse(text);
47 | if (!response.ok) {
48 | if (response.status === 401) {
49 | // auto logout if 401 response returned from api
50 | logout();
51 | location.reload(true);
52 | }
53 |
54 | const error = (data && data.message) || response.statusText;
55 | return Promise.reject(error);
56 | }
57 |
58 | return data;
59 | });
60 | }
--------------------------------------------------------------------------------
/src/_store/alert.module.js:
--------------------------------------------------------------------------------
1 | export const alert = {
2 | namespaced: true,
3 | state: {
4 | type: null,
5 | message: null
6 | },
7 | actions: {
8 | success({ commit }, message) {
9 | commit('success', message);
10 | },
11 | error({ commit }, message) {
12 | commit('error', message);
13 | },
14 | clear({ commit }) {
15 | commit('clear');
16 | }
17 | },
18 | mutations: {
19 | success(state, message) {
20 | state.type = 'alert-success';
21 | state.message = message;
22 | },
23 | error(state, message) {
24 | state.type = 'alert-danger';
25 | state.message = message;
26 | },
27 | clear(state) {
28 | state.type = null;
29 | state.message = null;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/_store/authentication.module.js:
--------------------------------------------------------------------------------
1 | import { userService } from '../_services';
2 | import { router } from '../_helpers';
3 |
4 | const user = JSON.parse(localStorage.getItem('user'));
5 | const initialState = user
6 | ? { status: { loggedIn: true }, user }
7 | : { status: {}, user: null };
8 |
9 | export const authentication = {
10 | namespaced: true,
11 | state: initialState,
12 | actions: {
13 | login({ dispatch, commit }, { username, password }) {
14 | commit('loginRequest', { username });
15 |
16 | userService.login(username, password)
17 | .then(
18 | user => {
19 | commit('loginSuccess', user);
20 | router.push('/');
21 | },
22 | error => {
23 | commit('loginFailure', error);
24 | dispatch('alert/error', error, { root: true });
25 | }
26 | );
27 | },
28 | logout({ commit }) {
29 | userService.logout();
30 | commit('logout');
31 | }
32 | },
33 | mutations: {
34 | loginRequest(state, user) {
35 | state.status = { loggingIn: true };
36 | state.user = user;
37 | },
38 | loginSuccess(state, user) {
39 | state.status = { loggedIn: true };
40 | state.user = user;
41 | },
42 | loginFailure(state) {
43 | state.status = {};
44 | state.user = null;
45 | },
46 | logout(state) {
47 | state.status = {};
48 | state.user = null;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/_store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 |
4 | import { alert } from './alert.module';
5 | import { authentication } from './authentication.module';
6 | import { users } from './users.module';
7 |
8 | Vue.use(Vuex);
9 |
10 | export const store = new Vuex.Store({
11 | modules: {
12 | alert,
13 | authentication,
14 | users
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/src/_store/users.module.js:
--------------------------------------------------------------------------------
1 | import { userService } from '../_services';
2 |
3 | export const users = {
4 | namespaced: true,
5 | state: {
6 | all: {}
7 | },
8 | actions: {
9 | getAll({ commit }) {
10 | commit('getAllRequest');
11 |
12 | userService.getAll()
13 | .then(
14 | users => commit('getAllSuccess', users),
15 | error => commit('getAllFailure', error)
16 | );
17 | }
18 | },
19 | mutations: {
20 | getAllRequest(state) {
21 | state.all = { loading: true };
22 | },
23 | getAllSuccess(state, users) {
24 | state.all = { items: users };
25 | },
26 | getAllFailure(state, error) {
27 | state.all = { error };
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/App.vue:
--------------------------------------------------------------------------------
1 |
2 | You're logged in with Vue + Vuex & JWT!!
14 | Hi {{user.firstName}}!
4 | Users from secure api end point:
6 | Loading users...
7 | ERROR: {{users.error}}
8 |
9 |
13 |
17 | Vue + Vuex - JWT Authentication Tutorial & Example 18 |
19 |20 | JasonWatmore.com 21 |
22 |