├── .babelrc ├── .gitignore ├── README.md ├── assets └── data │ └── foo.json ├── dist └── .gitkeep ├── favicon.ico ├── index.html ├── package.json └── src ├── App.vue ├── components └── navbar.vue ├── content ├── auth │ ├── Login.view.html │ ├── Login.vue │ └── Logout.vue └── default │ ├── About.vue │ ├── Main.vue │ └── NotFound.vue ├── main.js ├── router ├── config.js ├── paths.js └── routes.js ├── services ├── authService │ ├── index.js │ └── vuex │ │ └── auth │ │ ├── getters.js │ │ └── index.js ├── notificationService.js └── profileService.js └── vuex ├── api ├── getters.js └── index.js ├── notification ├── getters.js └── index.js └── store.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/build.js 4 | npm-debug.log 5 | /.idea 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue/VueResource/VueRouter/Vuex (4Vs) JWT-Auth 2 | 3 | > Esqueleto base para uma aplicação web com sistema de autenticação via JWT incluído 4 | 5 | > *Skeleton basis for a web application with authentication system via JWT included* 6 | 7 | ## Instalação *(Setup)* 8 | 9 | ``` bash 10 | # install dependencies 11 | npm install 12 | 13 | # serve with hot reload at localhost:8080 14 | npm run dev 15 | 16 | # build for production with minification 17 | npm run build 18 | ``` 19 | 20 | ## O que está incluso? *(What is included?)* 21 | 22 | * Vue 23 | * Vue-Router 24 | * Vue-Resource 25 | * Vuex 26 | * Vue-Validator 27 | * Vue-Strap 28 | * Bootstrap 3.3.6 29 | 30 | ## Estrutura da Aplicação *(Application Structure)* 31 | 32 | * /(root) 33 | * /assets - pasta de imagens e outros arquivos externos para a aplicação 34 | * /dist - o build final ficará aqui 35 | * /src - o código-fonte da aplicação 36 | * /content 37 | * /auth - componentes para autenticação 38 | * /default - componentes base/comuns 39 | * /router 40 | * config.js - configura o Vue-Router 41 | * paths.js - lista das URLs 42 | * routes.js - rotas da aplicação 43 | * /services 44 | * authService.js - 45 | * /vuex (1) 46 | * /api - módulo de api para o vuex 47 | * /auth - módulo de autenticação 48 | * /notification - módulo de notificações 49 | * store.js - Vuex store 50 | * App.vue - componente raiz da aplicação 51 | * main.js - ponto de entrada da aplicação 52 | * index.html - container HTML da aplicação 53 | 54 | (1) - os módulos Vuex criam cada um seus respectivos *state* e *mutations*, bem como seus *getters* e *actions*. 55 | 56 | ## ToDo: 57 | 58 | ``` 59 | - melhorar este readme 60 | - documentar o processo de autenticação usando JWT 61 | - exemplos de como criar novos módulos 62 | - muito mais! 63 | 64 | ``` 65 | 66 | Cya! Jorge ***jjsquad*** Junior \o/ 67 | 68 | ### Links: 69 | 70 | - [Vue.js](http://vuejs.org) 71 | - [Vue-Resource](https://github.com/vuejs/vue-resource) 72 | - [Vue-Router](https://github.com/vuejs/vue-router) 73 | - [Vuex](https://github.com/vuejs/vuex) 74 | - [Vue-Validator](https://github.com/vuejs/vue-validator) 75 | - [Vue-Strap](http://yuche.github.io/vue-strap) 76 | - [Bootstrap](http://getbootstrap.com) 77 | 78 | 79 | For more information see the [docs for vueify](https://github.com/vuejs/vueify). 80 | -------------------------------------------------------------------------------- /assets/data/foo.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": "a foo message" 3 | } -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjcodes78/vue-vuex-auth/f2a1e2a7cbe2d2e91d29b1825167ce868170e8c6/dist/.gitkeep -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjcodes78/vue-vuex-auth/f2a1e2a7cbe2d2e91d29b1825167ce868170e8c6/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue/Vuex com JWT-Auth 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-vuex-auth", 3 | "version": "0.0.1", 4 | "description": "Esqueleto base para uma aplicação web com sistema de autenticação via JWT-Auth", 5 | "author": "jjsquad ", 6 | "scripts": { 7 | "watchify": "watchify -vd -p browserify-hmr -e src/main.js -o dist/build.js", 8 | "serve": "http-server -c 1 -a localhost", 9 | "dev": "npm-run-all --parallel watchify serve", 10 | "build": "cross-env NODE_ENV=production browserify src/main.js | uglifyjs -c warnings=false -m > dist/build.js" 11 | }, 12 | "dependencies": { 13 | "bootstrap": "^3.3.6", 14 | "vue": "^1.0.0", 15 | "vue-resource": "^0.9.3", 16 | "vue-router": "^0.7.13", 17 | "vue-strap": "^1.0.11", 18 | "vue-validator": "^2.1.3", 19 | "vuex": "^1.0.0-rc" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.0.0", 23 | "babel-plugin-transform-runtime": "^6.0.0", 24 | "babel-preset-es2015": "^6.0.0", 25 | "babel-preset-stage-2": "^6.0.0", 26 | "babel-runtime": "^6.0.0", 27 | "babelify": "^7.2.0", 28 | "browserify": "^12.0.1", 29 | "browserify-hmr": "^0.3.1", 30 | "cross-env": "^1.0.6", 31 | "http-server": "^0.9.0", 32 | "jsonwebtoken": "^7.1.6", 33 | "npm-run-all": "^1.6.0", 34 | "uglify-js": "^2.5.0", 35 | "vueify": "^8.5.2", 36 | "watchify": "^3.4.0" 37 | }, 38 | "browserify": { 39 | "transform": [ 40 | "vueify", 41 | "babelify" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | -------------------------------------------------------------------------------- /src/components/navbar.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | -------------------------------------------------------------------------------- /src/content/auth/Login.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | Login 6 |
7 |
8 | 9 | 10 |
11 |

{{ error }}

12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 | 27 | 29 |
30 |
31 | 32 | 37 | 39 |
40 |
41 |
42 |
43 |
44 | 48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
-------------------------------------------------------------------------------- /src/content/auth/Login.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | -------------------------------------------------------------------------------- /src/content/auth/Logout.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/content/default/About.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /src/content/default/Main.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/content/default/NotFound.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import VueResource from "vue-resource" 3 | import VueRouter from "vue-router" 4 | 5 | import App from "./App.vue" 6 | import { routerConfig } from "./router/config" 7 | import VueValidator from "vue-validator" 8 | 9 | Vue.use(VueValidator) 10 | Vue.use(VueRouter) 11 | Vue.use(VueResource) 12 | 13 | let router = new VueRouter() 14 | 15 | //Configura o Router 16 | routerConfig(router) 17 | 18 | //Inicia o Router 19 | router.start(App, "App") -------------------------------------------------------------------------------- /src/router/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configura o Router: 3 | * - define as rotas e os componentes 4 | * - cria a rota padrão de redirecionamento 5 | * - e monitora a cada transição se há um usuário logado 6 | */ 7 | import routes from './routes' 8 | import authService from '../services/authService' 9 | import { HOME_URL, LOGIN_URL } from './paths' 10 | 11 | export function routerConfig(router) { 12 | 13 | router.map(routes) 14 | 15 | /** 16 | * A cada transição é verificada se: 17 | * - a rota precisa de autenticação para ser visitada 18 | * - se há usuário logado 19 | * - se há um token válido para refazer a autenticação 20 | */ 21 | router.beforeEach(function (transition) { 22 | 23 | let token = null 24 | 25 | if (!authService.isLoggedIn()) { 26 | token = localStorage.getItem('jwt-token') 27 | } 28 | 29 | if (transition.to.auth && token !== null && !authService.isLoggedIn()) { 30 | 31 | authService.setToken(token) 32 | return authService.getUserProfile() 33 | .then(function (res) { 34 | authService.setUser(res.data.user) 35 | transition.next() 36 | }).catch(function () { 37 | authService.remove() 38 | transition.redirect(LOGIN_URL) 39 | }) 40 | } else if (transition.to.auth && !authService.isLoggedIn()) { 41 | 42 | transition.redirect(LOGIN_URL) 43 | 44 | } else if (transition.to.guest && authService.isLoggedIn()) { 45 | 46 | transition.redirect(transition.from.path) 47 | 48 | } else { 49 | 50 | transition.next() 51 | 52 | } 53 | 54 | }) 55 | 56 | router.redirect({ 57 | '*': '/' 58 | }) 59 | 60 | } -------------------------------------------------------------------------------- /src/router/paths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 16/07/16. 3 | */ 4 | 5 | /* 6 | API SERVICES URLs 7 | */ 8 | export const API_BASE_URL = 'http://localhost:8000/api/v1' 9 | export const API_AUTH_URL = API_BASE_URL + '/auth' 10 | export const API_LOGOUT_URL = API_AUTH_URL + '/logout' 11 | export const API_USER_PROFILE_URL = API_BASE_URL + '/auth/profile' 12 | 13 | /* 14 | APPLICATION ROUTES URLs 15 | */ 16 | export const HOME_URL = '/' 17 | export const LOGIN_URL = '/auth/login' 18 | export const LOGOUT_URL = '/auth/logout' 19 | export const ABOUT_URL = '/about' 20 | 21 | // Navbar component links 22 | export const links = [ 23 | { title: 'Home', icon: 'glyphicon glyphicon-home', path: HOME_URL }, 24 | { title: 'About', icon: 'glyphicon glyphicon-info-sign', path: ABOUT_URL} 25 | ] -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 15/07/16. 3 | */ 4 | 5 | /** 6 | * Componentes das rotas 7 | */ 8 | import Main from '../content/default/Main.vue' 9 | import Login from '../content/auth/Login.vue' 10 | import Logout from '../content/auth/Logout.vue' 11 | import About from '../content/default/About.vue' 12 | import * as path from './paths' 13 | 14 | const routes = { 15 | [path.HOME_URL]: { 16 | component: Main, 17 | auth: true 18 | }, 19 | [path.LOGIN_URL]: { 20 | component: Login, 21 | guest: true 22 | }, 23 | [path.LOGOUT_URL]: { 24 | component: Logout, 25 | auth: true 26 | }, 27 | [path.ABOUT_URL]: { 28 | component: About, 29 | auth: true 30 | } 31 | } 32 | 33 | export default routes -------------------------------------------------------------------------------- /src/services/authService/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 16/07/16. 3 | */ 4 | import Vue from 'vue' 5 | import VueResource from 'vue-resource' 6 | import store from '../../vuex/store' 7 | import authenticated from './vuex/auth/getters' 8 | import { API_AUTH_URL, API_LOGOUT_URL, API_USER_PROFILE_URL } from '../../router/paths' 9 | 10 | Vue.use(VueResource) 11 | 12 | /** 13 | * Se o login for bem sucedido obtém-se o profile do usuário logado 14 | * e retorna a Promise com o resultado 15 | */ 16 | const getUserProfile = function() { 17 | return Vue.http({ 18 | url: API_USER_PROFILE_URL, 19 | method: 'GET', 20 | headers: { 21 | Authorization: 'Bearer ' + localStorage.getItem('jwt-token'), 22 | 'Access-Control-Allow-Origin': '*' //TODO: remover CORS 23 | } 24 | }) 25 | } 26 | 27 | const login = function (body) { 28 | return Vue.http({ 29 | url: API_AUTH_URL, 30 | method: "POST", 31 | body: body, 32 | headers: { 33 | 'Access-Control-Allow-Origin': '*' //TODO: remover CORS 34 | } 35 | }) 36 | } 37 | 38 | const logout = function () { 39 | return Vue.http({ 40 | url: API_LOGOUT_URL, 41 | method: 'GET', 42 | headers: { 43 | Authorization: 'Bearer ' + localStorage.getItem('jwt-token') 44 | } 45 | }) 46 | } 47 | 48 | const setToken = function (token) { 49 | localStorage.setItem('jwt-token', token) 50 | store.dispatch('SET_TOKEN', token) 51 | } 52 | 53 | const setUser = function (user) { 54 | store.dispatch('SET_USER', user) 55 | } 56 | 57 | const set = function (type, value) { 58 | switch (type) { 59 | case 'token': 60 | setToken(value) 61 | break 62 | case 'user': 63 | setUser(value) 64 | break 65 | } 66 | } 67 | 68 | const remove = function () { 69 | localStorage.removeItem('jwt-token') 70 | store.dispatch('REMOVE_TOKEN') 71 | store.dispatch('REMOVE_USER') 72 | } 73 | 74 | const getToken = function () { 75 | return store.state.auth.token 76 | } 77 | 78 | const getUser = function () { 79 | return store.state.auth.user 80 | } 81 | 82 | const isLoggedIn = function () { 83 | return store.state.auth.authenticated 84 | } 85 | 86 | export default { 87 | getUserProfile, 88 | login, 89 | logout, 90 | set, 91 | remove, 92 | isLoggedIn, 93 | setToken, 94 | setUser, 95 | getToken, 96 | getUser 97 | } -------------------------------------------------------------------------------- /src/services/authService/vuex/auth/getters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 15/07/16. 3 | */ 4 | 5 | /** 6 | * obtém o valor do token do state 7 | * @param state 8 | * @returns {token|*|null} 9 | */ 10 | const token = function (state) { 11 | return state.auth.token 12 | } 13 | 14 | /** 15 | * obtém o valor de user do state 16 | * @param state 17 | * @returns {*} 18 | */ 19 | const user = function (state) { 20 | return state.auth.user 21 | } 22 | 23 | /** 24 | * obtem o valor authenticated, se há um usuário logado 25 | * @param state 26 | * @returns {authenticated|*|boolean} 27 | */ 28 | const authenticated = function (state) { 29 | return state.auth.authenticated 30 | } 31 | 32 | export default { 33 | token, 34 | user, 35 | authenticated 36 | } -------------------------------------------------------------------------------- /src/services/authService/vuex/auth/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 15/07/16. 3 | */ 4 | 5 | const SET_USER = 'SET_USER' 6 | const REMOVE_USER = 'REMOVE_USER' 7 | const SET_TOKEN = 'SET_TOKEN' 8 | const REMOVE_TOKEN = 'REMOVE_TOKEN' 9 | 10 | const state = { 11 | token: null, 12 | user: {}, 13 | authenticated: false 14 | } 15 | 16 | const mutations = { 17 | [SET_USER] (state, user) { 18 | state.user = user 19 | state.authenticated = true 20 | }, 21 | 22 | [REMOVE_USER] (state) { 23 | state.user = {} 24 | state.authenticated = false 25 | }, 26 | 27 | [SET_TOKEN] (state, token) { 28 | state.token = token 29 | }, 30 | 31 | [REMOVE_TOKEN] (state) { 32 | state.token = null 33 | } 34 | } 35 | 36 | export default { 37 | state, 38 | mutations 39 | } -------------------------------------------------------------------------------- /src/services/notificationService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 18/07/16. 3 | */ 4 | 5 | import store from '../vuex/store' 6 | 7 | const CONNECTION_ERROR = { 8 | duration: 5000, 9 | type: 'danger', 10 | title: 'Oops! Erro', 11 | message: 'Verifique a conexão e tente novamente.', 12 | description: 'Não foi possível estabelecer uma conexão com o serviço. (ERR_CONNECTION_REFUSED)', 13 | icon: 'glyphicon glyphicon-remove-sign' 14 | } 15 | 16 | const INTERNAL_SERVER_ERROR = { 17 | duration: 5000, 18 | type: 'danger', 19 | title: 'Oops! Erro', 20 | message: 'Erro interno do servidor.', 21 | description: 'Não foi possível estabelecer uma conexão com o serviço. (INTERNAL_SERVER_ERROR)', 22 | icon: 'glyphicon glyphicon-remove-sign' 23 | } 24 | 25 | // Notificações de login 26 | const USER_LOGGED_IN = { 27 | duration: 3000, 28 | type: 'success', 29 | title: 'OK!', 30 | message: 'Login efetuado com sucesso!', 31 | icon: 'glyphicon glyphicon-ok-sign' 32 | } 33 | 34 | const USER_LOGGED_OUT = { 35 | duration: 3000, 36 | type: 'success', 37 | title: 'OK!', 38 | message: 'Logout efetuado com sucesso!', 39 | description: 'Faça login novamente para voltar a utilizar a aplicação.', 40 | icon: 'glyphicon glyphicon-ok-sign' 41 | } 42 | 43 | const INVALID_TOKEN = { 44 | duration: 5000, 45 | type: 'danger', 46 | title: 'Oops! Erro', 47 | message: 'E-mail e/ou senha inválidos.', 48 | description: 'Contate o administrador do sistema. (UNAUTHORIZED)', 49 | icon: 'glyphicon glyphicon-remove-sign' 50 | } 51 | 52 | const USER_UNAUTHORIZED = { 53 | duration: 5000, 54 | type: 'danger', 55 | title: 'Oops! Erro', 56 | message: 'Token de acesso inválido.', 57 | description: 'Efetue login novamente ou contate o administrador do sistema. (INVALID_TOKEN)', 58 | icon: 'glyphicon glyphicon-remove-sign' 59 | } 60 | 61 | const ACCESS_FORBIDDEN = 'ACCESS_FORBIDDEN' // status 403 62 | const NOT_FOUND = 'NOT_FOUND' // status 404 63 | 64 | const show = function (options) { 65 | store.dispatch('SHOW_NOTIFICATION', options) 66 | } 67 | const hide = function () { 68 | store.dispatch('HIDE_NOTIFICATION') 69 | } 70 | 71 | export default { 72 | show, 73 | hide, 74 | CONNECTION_ERROR, 75 | INTERNAL_SERVER_ERROR, 76 | USER_LOGGED_IN, 77 | USER_LOGGED_OUT, 78 | INVALID_TOKEN, 79 | USER_UNAUTHORIZED 80 | } -------------------------------------------------------------------------------- /src/services/profileService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 17/07/16. 3 | */ 4 | -------------------------------------------------------------------------------- /src/vuex/api/getters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 16/07/16. 3 | */ 4 | 5 | const status = function (state) { 6 | return state.api.status 7 | } 8 | 9 | export default { 10 | status 11 | } -------------------------------------------------------------------------------- /src/vuex/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 16/07/16. 3 | */ 4 | 5 | const SET_API_STATUS = 'SET_API_STATUS' 6 | 7 | const state = { 8 | status: false 9 | } 10 | 11 | const mutations = { 12 | [SET_API_STATUS] (state, status) { 13 | state.status = status 14 | } 15 | } 16 | 17 | export default { 18 | state, 19 | mutations 20 | } -------------------------------------------------------------------------------- /src/vuex/notification/getters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 16/07/16. 3 | */ 4 | 5 | const show = function (state) { 6 | return state.notification.show 7 | } 8 | 9 | const options = function (state) { 10 | return state.notification.options 11 | } 12 | 13 | export default { 14 | show, 15 | options 16 | } -------------------------------------------------------------------------------- /src/vuex/notification/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by squad on 16/07/16. 3 | */ 4 | 5 | // Notificaçoes comuns 6 | const SHOW_NOTIFICATION = 'SHOW_NOTIFICATION' 7 | const HIDE_NOTIFICATION = 'HIDE_NOTIFICATION' 8 | 9 | const state = { 10 | show: false, 11 | options: {} 12 | } 13 | 14 | const mutations = { 15 | [HIDE_NOTIFICATION] (state) { 16 | state.show = false 17 | }, 18 | 19 | [SHOW_NOTIFICATION] (state, notification) { 20 | state.options = notification 21 | state.show = true 22 | } 23 | } 24 | 25 | export default { 26 | state, 27 | mutations 28 | } -------------------------------------------------------------------------------- /src/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'Vuex' 3 | import api from './api' 4 | import auth from '../services/authService/vuex/auth' 5 | import notification from './notification' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | api, 12 | auth, 13 | notification 14 | } 15 | }) --------------------------------------------------------------------------------