├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── build ├── karma.conf.js ├── webpack.base.config.js ├── webpack.dev.config.js └── webpack.prod.config.js ├── index.html ├── package.json ├── server ├── .gitignore ├── README.md ├── anonymous-routes.js ├── config.json ├── package.json ├── protected-routes.js ├── quoter.js ├── quotes.json ├── server.js ├── statusError.js └── user-routes.js └── src ├── App.vue ├── assets └── logo.png ├── components ├── home.vue ├── login.vue ├── secretQuote.vue └── signup.vue ├── jwt └── index.js └── main.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": ["transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{vue,js,json,html,yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | 7 | "ecmaFeatures": { 8 | "arrowFunctions": true, 9 | "destructuring": true, 10 | "classes": true, 11 | "defaultParams": true, 12 | "blockBindings": true, 13 | "modules": true, 14 | "objectLiteralComputedProperties": true, 15 | "objectLiteralShorthandMethods": true, 16 | "objectLiteralShorthandProperties": true, 17 | "restParams": true, 18 | "spread": true, 19 | "templateStrings": true 20 | }, 21 | 22 | "rules": { 23 | "accessor-pairs": 2, 24 | "array-bracket-spacing": 0, 25 | "block-scoped-var": 0, 26 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 27 | "camelcase": 0, 28 | "comma-dangle": [2, "never"], 29 | "comma-spacing": [2, { "before": false, "after": true }], 30 | "comma-style": [2, "last"], 31 | "complexity": 0, 32 | "computed-property-spacing": 0, 33 | "consistent-return": 0, 34 | "consistent-this": 0, 35 | "constructor-super": 2, 36 | "curly": [2, "multi-line"], 37 | "default-case": 0, 38 | "dot-location": [2, "property"], 39 | "dot-notation": 0, 40 | "eol-last": 2, 41 | "eqeqeq": [2, "allow-null"], 42 | "func-names": 0, 43 | "func-style": 0, 44 | "generator-star-spacing": [2, { "before": true, "after": true }], 45 | "guard-for-in": 0, 46 | "handle-callback-err": [2, "^(err|error)$" ], 47 | "indent": [2, 2, { "SwitchCase": 1 }], 48 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 49 | "linebreak-style": 0, 50 | "lines-around-comment": 0, 51 | "max-nested-callbacks": 0, 52 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 53 | "new-parens": 2, 54 | "newline-after-var": 0, 55 | "no-alert": 0, 56 | "no-array-constructor": 2, 57 | "no-caller": 2, 58 | "no-catch-shadow": 0, 59 | "no-cond-assign": 2, 60 | "no-console": 0, 61 | "no-constant-condition": 0, 62 | "no-continue": 0, 63 | "no-control-regex": 2, 64 | "no-debugger": 2, 65 | "no-delete-var": 2, 66 | "no-div-regex": 0, 67 | "no-dupe-args": 2, 68 | "no-dupe-keys": 2, 69 | "no-duplicate-case": 2, 70 | "no-else-return": 0, 71 | "no-empty": 0, 72 | "no-empty-character-class": 2, 73 | "no-empty-label": 2, 74 | "no-eq-null": 0, 75 | "no-eval": 2, 76 | "no-ex-assign": 2, 77 | "no-extend-native": 2, 78 | "no-extra-bind": 2, 79 | "no-extra-boolean-cast": 2, 80 | "no-extra-parens": 0, 81 | "no-extra-semi": 0, 82 | "no-fallthrough": 2, 83 | "no-floating-decimal": 2, 84 | "no-func-assign": 2, 85 | "no-implied-eval": 2, 86 | "no-inline-comments": 0, 87 | "no-inner-declarations": [2, "functions"], 88 | "no-invalid-regexp": 2, 89 | "no-irregular-whitespace": 2, 90 | "no-iterator": 2, 91 | "no-label-var": 2, 92 | "no-labels": 2, 93 | "no-lone-blocks": 2, 94 | "no-lonely-if": 0, 95 | "no-loop-func": 0, 96 | "no-mixed-requires": 0, 97 | "no-mixed-spaces-and-tabs": 2, 98 | "no-multi-spaces": 2, 99 | "no-multi-str": 2, 100 | "no-multiple-empty-lines": [2, { "max": 1 }], 101 | "no-native-reassign": 2, 102 | "no-negated-in-lhs": 2, 103 | "no-nested-ternary": 0, 104 | "no-new": 2, 105 | "no-new-func": 0, 106 | "no-new-object": 2, 107 | "no-new-require": 2, 108 | "no-new-wrappers": 2, 109 | "no-obj-calls": 2, 110 | "no-octal": 2, 111 | "no-octal-escape": 2, 112 | "no-param-reassign": 0, 113 | "no-path-concat": 0, 114 | "no-process-env": 0, 115 | "no-process-exit": 0, 116 | "no-proto": 0, 117 | "no-redeclare": 2, 118 | "no-regex-spaces": 2, 119 | "no-restricted-modules": 0, 120 | "no-return-assign": 2, 121 | "no-script-url": 0, 122 | "no-self-compare": 2, 123 | "no-sequences": 2, 124 | "no-shadow": 0, 125 | "no-shadow-restricted-names": 2, 126 | "no-spaced-func": 2, 127 | "no-sparse-arrays": 2, 128 | "no-sync": 0, 129 | "no-ternary": 0, 130 | "no-this-before-super": 2, 131 | "no-throw-literal": 2, 132 | "no-trailing-spaces": 2, 133 | "no-undef": 2, 134 | "no-undef-init": 2, 135 | "no-undefined": 0, 136 | "no-underscore-dangle": 0, 137 | "no-unexpected-multiline": 2, 138 | "no-unneeded-ternary": 2, 139 | "no-unreachable": 2, 140 | "no-unused-expressions": 0, 141 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 142 | "no-use-before-define": 0, 143 | "no-var": 0, 144 | "no-void": 0, 145 | "no-warning-comments": 0, 146 | "no-with": 2, 147 | "object-curly-spacing": 0, 148 | "object-shorthand": 0, 149 | "one-var": [2, { "initialized": "never" }], 150 | "operator-assignment": 0, 151 | "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], 152 | "padded-blocks": 0, 153 | "prefer-const": 0, 154 | "quote-props": 0, 155 | "quotes": [2, "single", "avoid-escape"], 156 | "radix": 2, 157 | "semi": [2, "never"], 158 | "semi-spacing": 0, 159 | "sort-vars": 0, 160 | "space-after-keywords": [2, "always"], 161 | "space-before-blocks": [2, "always"], 162 | "space-before-function-paren": [2, "always"], 163 | "space-in-parens": [2, "never"], 164 | "space-infix-ops": 2, 165 | "space-return-throw-case": 2, 166 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 167 | "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }], 168 | "strict": 0, 169 | "use-isnan": 2, 170 | "valid-jsdoc": 0, 171 | "valid-typeof": 2, 172 | "vars-on-top": 0, 173 | "wrap-iife": [2, "any"], 174 | "wrap-regex": 0, 175 | "yoda": [2, "never"] 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-jwt-vueRouter-vueResource 2 | 使用vue component的方式实现的单页需登录认证app,认证方式直接使用了auth0提供的nodejs jwt认证模块[nodejs-jwt-authentication-sample](https://github.com/auth0/nodejs-jwt-authentication-sample),页面切换采用了vue-router,和服务器端的通信认证使用了vue-resource。 3 | 4 | ##使用方法 5 | 6 | 1、安装认证模块(server文件夹)需要使用的依赖: 7 | 8 | cd ./server 9 | npm install 10 | 11 | 2、认证模块依赖安装成功后,回到主目录安装项目依赖: 12 | 13 | cd .. 14 | npm install 15 | 16 | 3、启动node服务器: 17 | 18 | node ./server/server.js 19 | 20 | 成功启动后服务器会监听在`http://localhost:3001` 21 | 22 | 4、打开另一个终端,运行项目开发配置: 23 | 24 | npm run dev 25 | 26 | 运行成功后,打开浏览器,进入`http://localhost:8080`即可查看项目文件(因为第一次需要加载热加载模块,第一次打开速度会比较慢)。 27 | 28 | ##预览 29 | 30 | ![](http://i.imgur.com/ZDn8ViI.png) 31 | 32 | ![](http://i.imgur.com/Cvqzq4V.png) 33 | 34 | ![](http://i.imgur.com/xahfcmD.png) -------------------------------------------------------------------------------- /build/karma.conf.js: -------------------------------------------------------------------------------- 1 | // we can just use the exact same webpack config by requiring it 2 | // but make sure to delete the normal entry 3 | var webpackConf = require('./webpack.base.config') 4 | delete webpackConf.entry 5 | 6 | module.exports = function (config) { 7 | config.set({ 8 | browsers: ['PhantomJS'], 9 | frameworks: ['jasmine'], 10 | reporters: ['spec'], 11 | // this is the entry file for all our tests. 12 | files: ['../test/unit/index.js'], 13 | // we will pass the entry file to webpack for bundling. 14 | preprocessors: { 15 | '../test/unit/index.js': ['webpack'] 16 | }, 17 | webpack: webpackConf, 18 | webpackMiddleware: { 19 | noInfo: true 20 | }, 21 | singleRun: true 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /build/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['./src/main.js', './src/jwt/index.js'], 3 | output: { 4 | path: './dist', 5 | publicPath: 'dist/', 6 | filename: 'build.js' 7 | }, 8 | module: { 9 | loaders: [ 10 | { 11 | test: /\.vue$/, 12 | loader: 'vue' 13 | }, 14 | { 15 | test: /\.js$/, 16 | //loader: 'babel!eslint', 17 | loader: 'babel', 18 | // make sure to exclude 3rd party code in node_modules 19 | exclude: /node_modules/ 20 | }, 21 | { 22 | // edit this for additional asset file types 23 | test: /\.(png|jpg|gif)$/, 24 | loader: 'url', 25 | query: { 26 | // inline files smaller then 10kb as base64 dataURL 27 | limit: 10000, 28 | // fallback to file-loader with this naming scheme 29 | name: '[name].[ext]?[hash]' 30 | } 31 | } 32 | ] 33 | }, 34 | // vue-loader config: 35 | // lint all JavaScript inside *.vue files with ESLint 36 | // make sure to adjust your .eslintrc 37 | vue: { 38 | loaders: { 39 | //js: 'babel!eslint' 40 | js: 'babel' 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | var config = require('./webpack.base.config') 2 | 3 | config.devtool = 'eval-source-map' 4 | 5 | config.devServer = { 6 | noInfo: true 7 | } 8 | 9 | module.exports = config 10 | -------------------------------------------------------------------------------- /build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var config = require('./webpack.base.config') 3 | 4 | config.plugins = (config.plugins || []).concat([ 5 | // this allows uglify to strip all warnings 6 | // from Vue.js source code. 7 | new webpack.DefinePlugin({ 8 | 'process.env': { 9 | NODE_ENV: '"production"' 10 | } 11 | }), 12 | // This minifies not only JavaScript, but also 13 | // the templates (with html-minifier) and CSS (with cssnano)! 14 | new webpack.optimize.UglifyJsPlugin({ 15 | compress: { 16 | warnings: false 17 | } 18 | }), 19 | new webpack.optimize.OccurenceOrderPlugin() 20 | ]) 21 | 22 | module.exports = config 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue jwt 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-jwt-vueRouter", 3 | "version": "1.0.0", 4 | "description": "a simple jwt use vue component vueRouter vueRouter", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --inline --hot --config build/webpack.dev.config.js", 8 | "build": "webpack --progress --hide-modules --config build/webpack.prod.config.js", 9 | "test": "karma start build/karma.conf.js" 10 | }, 11 | "author": "lijiahao", 12 | "license": "MIT", 13 | "dependencies": { 14 | "vue": "^1.0.16", 15 | "vue-router": "^0.7.5", 16 | "vue-resource": "^0.1.17", 17 | "bootstrap": "^3.3.5" 18 | }, 19 | "devDependencies": { 20 | "babel-core": "^6.1.2", 21 | "babel-loader": "^6.1.0", 22 | "babel-plugin-transform-runtime": "^6.1.2", 23 | "babel-preset-es2015": "^6.1.2", 24 | "babel-preset-stage-0": "^6.1.2", 25 | "babel-runtime": "^5.8.0", 26 | "css-loader": "^0.23.0", 27 | "eslint": "^1.10.3", 28 | "eslint-loader": "^1.3.0", 29 | "file-loader": "^0.8.4", 30 | "function-bind": "^1.0.2", 31 | "inject-loader": "^2.0.1", 32 | "jade": "^1.11.0", 33 | "jasmine-core": "^2.4.1", 34 | "karma": "^0.13.15", 35 | "karma-jasmine": "^0.3.6", 36 | "karma-phantomjs-launcher": "^0.2.1", 37 | "karma-spec-reporter": "0.0.23", 38 | "karma-webpack": "^1.7.0", 39 | "phantomjs": "^1.9.19", 40 | "stylus-loader": "^1.4.0", 41 | "template-html-loader": "0.0.3", 42 | "url-loader": "^0.5.7", 43 | "vue-hot-reload-api": "^1.2.0", 44 | "vue-html-loader": "^1.0.0", 45 | "vue-loader": "^8.0.0", 46 | "vue-style-loader": "^1.0.0", 47 | "webpack": "^1.12.2", 48 | "webpack-dev-server": "^1.12.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # NodeJS JWT Authentication sample 2 | 3 | This is a NodeJS API that supports username and password authentication with JWTs and has APIs that return Chuck Norris phrases. How awesome is that? 4 | 5 | ## Available APIs 6 | 7 | ### User APIs 8 | 9 | #### POST `/users` 10 | 11 | You can do a POST to `/users` to create a new user. 12 | 13 | The body must have: 14 | 15 | * `username`: The username 16 | * `password`: The password 17 | * `extra`: Some extra information you want to save from the user (It's a string). This could be a color or anything at all. 18 | 19 | It returns the following: 20 | 21 | ```json 22 | { 23 | "id_token": {jwt} 24 | } 25 | ``` 26 | 27 | The JWT is signed with the secret located at the `config.json` file. That JWT will contain the `username` and the `extra` information sent. 28 | 29 | #### POST `/sessions/create` 30 | 31 | You can do a POST to `/sessions/create` to log a user in. 32 | 33 | The body must have: 34 | 35 | * `username`: The username 36 | * `password`: The password 37 | 38 | It returns the following: 39 | 40 | ```json 41 | { 42 | "id_token": {jwt} 43 | } 44 | ``` 45 | 46 | The JWT is signed with the secret located at the `config.json` file. That JWT will contain the `username` and the `extra` information that you sent at signup time. 47 | 48 | ### Quotes API 49 | 50 | #### GET `/api/random-quote` 51 | 52 | It returns a String with a Random quote from Chuck Norris. It doesn't require authentication. 53 | 54 | #### GET `/api/protected/random-quote` 55 | 56 | It returns a String with a Random quote from Chuck Norris. It requires authentication. 57 | 58 | The JWT must be sent on the `Authorization` header as follows: `Authorization: Bearer {jwt}` 59 | 60 | ## Running it 61 | 62 | Just clone the repository, run `npm install` and then `node server.js`. That's it :). 63 | 64 | If you want to run it on another port, just run `PORT=3001 node server.js` to run it on port 3001 for example 65 | 66 | ## Issue Reporting 67 | 68 | If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. 69 | 70 | ## License 71 | 72 | MIT 73 | 74 | ## What is Auth0? 75 | 76 | Auth0 helps you to: 77 | 78 | * Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, amont others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. 79 | * Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**. 80 | * Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user. 81 | * Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely. 82 | * Analytics of how, when and where users are logging in. 83 | * Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules). 84 | 85 | ## Create a free account in Auth0 86 | 87 | 1. Go to [Auth0](https://auth0.com) and click Sign Up. 88 | 2. Use Google, GitHub or Microsoft Account to login. 89 | -------------------------------------------------------------------------------- /server/anonymous-routes.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | quoter = require('./quoter'); 3 | 4 | var app = module.exports = express.Router(); 5 | 6 | app.get('/api/random-quote', function(req, res) { 7 | res.status(200).send(quoter.getRandomOne()); 8 | }); 9 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "secret": "ngEurope rocks!" 3 | } 4 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "in-memory-todo", 3 | "version": "0.1.0", 4 | "description": "An In memory todo for Logged in and not Logged in users", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/auth0/in-memory-todo.git" 12 | }, 13 | "keywords": [ 14 | "todo", 15 | "in", 16 | "memory", 17 | "jwt", 18 | "auth0" 19 | ], 20 | "author": "Martin Gontovnikas", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/auth0/in-memory-todo/issues" 24 | }, 25 | "homepage": "https://github.com/auth0/in-memory-todo", 26 | "dependencies": { 27 | "body-parser": "^1.6.5", 28 | "compression": "^1.0.11", 29 | "cors": "^2.4.1", 30 | "dotenv": "^0.4.0", 31 | "errorhandler": "^1.1.1", 32 | "express": "^4.8.5", 33 | "express-jwt": "^0.3.1", 34 | "jsonwebtoken": "^5.0.1", 35 | "lodash": "^2.4.1", 36 | "morgan": "^1.2.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/protected-routes.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | jwt = require('express-jwt'), 3 | config = require('./config'), 4 | quoter = require('./quoter'); 5 | 6 | var app = module.exports = express.Router(); 7 | 8 | var jwtCheck = jwt({ 9 | secret: config.secret 10 | }); 11 | 12 | app.use('/api/protected', jwtCheck); 13 | 14 | app.get('/api/protected/random-quote', function(req, res) { 15 | res.status(200).send(quoter.getRandomOne()); 16 | }); 17 | -------------------------------------------------------------------------------- /server/quoter.js: -------------------------------------------------------------------------------- 1 | var quotes = require('./quotes.json'); 2 | 3 | exports.getRandomOne = function() { 4 | var totalAmount = quotes.length; 5 | var rand = Math.ceil(Math.random() * totalAmount); 6 | return quotes[rand]; 7 | } 8 | -------------------------------------------------------------------------------- /server/quotes.json: -------------------------------------------------------------------------------- 1 | ["Chuck Norris doesn't call the wrong number. You answer the wrong phone.", 2 | "Chuck Norris has already been to Mars; that's why there are no signs of life.", 3 | "Chuck Norris and Superman once fought each other on a bet. The loser had to start wearing his underwear on the outside of his pants.", 4 | "Some magicans can walk on water, Chuck Norris can swim through land.", 5 | "Chuck Norris once urinated in a semi truck's gas tank as a joke....that truck is now known as Optimus Prime.", 6 | "Chuck Norris doesn't flush the toilet, he scares the sh*t out of it", 7 | "Chuck Norris counted to infinity - twice.", 8 | "Chuck Norris can cut through a hot knife with butter", 9 | "Chuck Norris is the reason why Waldo is hiding.", 10 | "Death once had a near-Chuck Norris experience", 11 | "When the Boogeyman goes to sleep every night, he checks his closet for Chuck Norris.", 12 | "Chuck Norris can slam a revolving door.", 13 | "Chuck Norris once kicked a horse in the chin. Its decendants are known today as Giraffes.", 14 | "Chuck Norris will never have a heart attack. His heart isn't nearly foolish enough to attack him.", 15 | "Chuck Norris once got bit by a rattle snake........ After three days of pain and agony ..................the rattle snake died", 16 | "Chuck Norris can win a game of Connect Four in only three moves.", 17 | "When Chuck Norris does a pushup, he isn't lifting himself up, he's pushing the Earth down.", 18 | "There is no theory of evolution. Just a list of animals Chuck Norris allows to live.", 19 | "Chuck Norris can light a fire by rubbing two ice-cubes together.", 20 | "Chuck Norris doesn’t wear a watch. HE decides what time it is.", 21 | "The original title for Alien vs. Predator was Alien and Predator vs Chuck Norris.", 22 | "The film was cancelled shortly after going into preproduction. No one would pay nine dollars to see a movie fourteen seconds long.", 23 | "Chuck Norris doesn't read books. He stares them down until he gets the information he wants.", 24 | "Chuck Norris made a Happy Meal cry.", 25 | "Outer space exists because it's afraid to be on the same planet with Chuck Norris.", 26 | "If you spell Chuck Norris in Scrabble, you win. Forever.", 27 | "Chuck Norris can make snow angels on a concrete slab.", 28 | "Chuck Norris destroyed the periodic table, because Chuck Norris only recognizes the element of surprise.", 29 | "Chuck Norris has to use a stunt double when he does crying scenes.", 30 | "Chuck Norris' hand is the only hand that can beat a Royal Flush.", 31 | "There is no theory of evolution. Just a list of creatures Chuck Norris has allowed to live.", 32 | "Chuck Norris does not sleep. He waits.", 33 | "Chuck Norris tells a GPS which way to go.", 34 | "Some people wear Superman pajamas. Superman wears Chuck Norris pajamas.", 35 | "Chuck Norris's tears cure cancer ..... to bad he has never cried", 36 | "Chuck Norris doesn't breathe, he holds air hostage.", 37 | "Chuck Norris had a staring contest with Medusa, and won.", 38 | "When life hands Chuck Norris lemons, he makes orange juice.", 39 | "When Chuck Norris goes on a picnic, the ants bring him food.", 40 | "Chuck Norris gives Freddy Krueger nightmares.", 41 | "They once made a Chuck Norris toilet paper, but there was a problem: It wouldn't take shit from anybody.", 42 | "Chuck Norris can punch a cyclops between the eyes.", 43 | "Chuck Norris doesn't mow his lawn, he stands on the porch and dares it to grow", 44 | "Chuck Norris put out a forest fire. using only gasoline", 45 | "Chuck Norris CAN believe it's not butter.", 46 | "Custom t-shirts provided by Spreadshirt"] 47 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var logger = require('morgan'), 2 | cors = require('cors'), 3 | http = require('http'), 4 | express = require('express'), 5 | errorhandler = require('errorhandler'), 6 | cors = require('cors'), 7 | dotenv = require('dotenv'), 8 | bodyParser = require('body-parser'); 9 | 10 | var app = express(); 11 | 12 | dotenv.load(); 13 | 14 | // Parsers 15 | // old version of line 16 | // app.use(bodyParser.urlencoded()); 17 | // new version of line 18 | app.use(bodyParser.urlencoded({ extended: true })); 19 | app.use(bodyParser.json()); 20 | app.use(cors()); 21 | 22 | app.use(function(err, req, res, next) { 23 | if (err.name === 'StatusError') { 24 | res.send(err.status, err.message); 25 | } else { 26 | next(err); 27 | } 28 | }); 29 | 30 | if (process.env.NODE_ENV === 'development') { 31 | app.use(logger('dev')); 32 | app.use(errorhandler()) 33 | } 34 | 35 | app.use(require('./anonymous-routes')); 36 | app.use(require('./protected-routes')); 37 | app.use(require('./user-routes')); 38 | 39 | var port = process.env.PORT || 3001; 40 | 41 | http.createServer(app).listen(port, function (err) { 42 | console.log('listening in http://localhost:' + port); 43 | }); 44 | 45 | -------------------------------------------------------------------------------- /server/statusError.js: -------------------------------------------------------------------------------- 1 | function StatusError(msg, status) { 2 | var err = Error.call(this, msg); 3 | err.status = status; 4 | err.name = 'StatusError'; 5 | return err; 6 | } 7 | 8 | 9 | StatusError.prototype = Object.create(Error.prototype, { 10 | constructor: { value: StatusError } 11 | }); 12 | 13 | module.exports = StatusError; 14 | 15 | -------------------------------------------------------------------------------- /server/user-routes.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | _ = require('lodash'), 3 | config = require('./config'), 4 | jwt = require('jsonwebtoken'); 5 | 6 | var app = module.exports = express.Router(); 7 | 8 | // XXX: This should be a database of users :). 9 | var users = [{ 10 | id: 1, 11 | username: 'gonto', 12 | password: 'gonto' 13 | }]; 14 | 15 | function createToken(user) { 16 | return jwt.sign(_.omit(user, 'password'), config.secret, { expiresInMinutes: 60*5 }); 17 | } 18 | 19 | function getUserScheme(req) { 20 | 21 | var username; 22 | var type; 23 | var userSearch = {}; 24 | 25 | // The POST contains a username and not an email 26 | if(req.body.username) { 27 | username = req.body.username; 28 | type = 'username'; 29 | userSearch = { username: username }; 30 | } 31 | // The POST contains an email and not an username 32 | else if(req.body.email) { 33 | username = req.body.email; 34 | type = 'email'; 35 | userSearch = { email: username }; 36 | } 37 | 38 | return { 39 | username: username, 40 | type: type, 41 | userSearch: userSearch 42 | } 43 | } 44 | 45 | app.post('/users', function(req, res) { 46 | 47 | var userScheme = getUserScheme(req); 48 | 49 | if (!userScheme.username || !req.body.password) { 50 | return res.status(400).send("You must send the username and the password"); 51 | } 52 | 53 | if (_.find(users, userScheme.userSearch)) { 54 | return res.status(400).send("A user with that username already exists"); 55 | } 56 | 57 | var profile = _.pick(req.body, userScheme.type, 'password', 'extra'); 58 | profile.id = _.max(users, 'id').id + 1; 59 | 60 | users.push(profile); 61 | 62 | res.status(201).send({ 63 | id_token: createToken(profile) 64 | }); 65 | }); 66 | 67 | app.post('/sessions/create', function(req, res) { 68 | 69 | var userScheme = getUserScheme(req); 70 | 71 | if (!userScheme.username || !req.body.password) { 72 | return res.status(400).send("You must send the username and the password"); 73 | } 74 | 75 | var user = _.find(users, userScheme.userSearch); 76 | 77 | if (!user) { 78 | return res.status(401).send({message:"The username or password don't match", user: user}); 79 | } 80 | 81 | if (user.password !== req.body.password) { 82 | return res.status(401).send("The username or password don't match"); 83 | } 84 | 85 | res.status(201).send({ 86 | id_token: createToken(user) 87 | }); 88 | }); -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 34 | 35 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Geocld/vue-jwt-vueRouter-vueResource/ae95dcfa1643c8c49e07a00f4a0a43ceb973a924/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /src/components/login.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 41 | -------------------------------------------------------------------------------- /src/components/secretQuote.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | -------------------------------------------------------------------------------- /src/components/signup.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 41 | -------------------------------------------------------------------------------- /src/jwt/index.js: -------------------------------------------------------------------------------- 1 | import {router} from '../main' 2 | 3 | const API_URL = 'http://localhost:3001/' 4 | const LOGIN_URL = API_URL + 'sessions/create/' 5 | const SIGNUP_URL = API_URL + 'users/' 6 | 7 | export default { 8 | user: { 9 | authenticated: false 10 | }, 11 | 12 | login(context, creds, redirect) { 13 | context.$http.post(LOGIN_URL, creds, (data) => { 14 | localStorage.setItem('id_token', data.id_token) 15 | this.user.authenticated = true 16 | 17 | if (redirect) { 18 | router.go(redirect) 19 | } 20 | }).error((err) => { 21 | //context.error = err 22 | context.error = '用户不存在或密码不正确!' 23 | }) 24 | }, 25 | 26 | signup(context, creds, redirect) { 27 | context.$http.post(SIGNUP_URL, creds, (data) => { 28 | localStorage.setItem('id_token', data.id_token) 29 | this.user.authenticated = true 30 | if (redirect) { 31 | router.go(redirect) 32 | } 33 | }).error((err) => { 34 | //context.error = err 35 | context.error = '用户名已注册' 36 | }) 37 | }, 38 | 39 | logout() { 40 | localStorage.removeItem('id_token') 41 | this.user.authenticated = false 42 | }, 43 | 44 | checkAuth() { 45 | var jwt = localStorage.getItem('id_token') 46 | if (jwt) { 47 | this.user.authenticated = true 48 | } 49 | else { 50 | this.user.authenticated = false 51 | } 52 | }, 53 | 54 | getAuthHeader() { 55 | return { 56 | 'Authorization': 'Bearer ' + localStorage.getItem('id_token') 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import home from './components/home.vue' 4 | import login from './components/login.vue' 5 | import signup from './components/signup.vue' 6 | import secretquote from './components/secretquote.vue' 7 | import VueRouter from 'vue-router' 8 | import VueResource from 'vue-resource' 9 | import auth from './jwt' 10 | Vue.use(VueRouter) 11 | Vue.use(VueResource) 12 | 13 | //vue-resource设置搜权初始值 14 | Vue.http.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('id_token'); 15 | 16 | // 当app启动时验证用户的登陆状态 17 | auth.checkAuth() 18 | 19 | export var router = new VueRouter() 20 | //定义路由规则 21 | router.map ({ 22 | '/home': { 23 | component: home 24 | }, 25 | 'secretquote': { 26 | component: secretquote 27 | }, 28 | '/login': { 29 | component: login 30 | }, 31 | '/signup': { 32 | component: signup 33 | } 34 | }) 35 | //为路由定义全局重定向规则,将任意未匹配路径重定向到/home页面 36 | router.redirect({ 37 | '*': '/home' 38 | }) 39 | //路由器会创建一个App实例,并挂载到#app对应的元素上 40 | router.start(App, '#app') 41 | 42 | --------------------------------------------------------------------------------