Skip to content

Commit 3317296

Browse files
committed
Add an initAuth Nuxt helper
1 parent d3b2bc2 commit 3317296

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,80 @@ The following actions are included in the `auth` module:
445445
- `authenticate`: Same as `feathersClient.authenticate()`
446446
- `logout`: Same as `feathersClient.logout()`
447447

448+
## Working with Auth & Nuxt
449+
450+
`[email protected]` ships with utilities that help with Nuxt auth related to JSON Web Tokens (JWT). The most important utility is the `initAuth` utility. It's for use during Nuxt's `nuxtServerInit` method, and sets up auth data automatically. Here's an example store that uses it:
451+
452+
```js
453+
import Vuex from 'vuex'
454+
import feathersClient from './feathers-client'
455+
import feathersVuex, { initAuth } from 'feathers-vuex'
456+
457+
const { service, auth } = feathersVuex(feathersClient)
458+
459+
const createStore = () => {
460+
return new Vuex.Store({
461+
state: {},
462+
mutations: {
463+
increment (state) {
464+
state.counter++
465+
}
466+
},
467+
actions: {
468+
nuxtServerInit ({ commit, dispatch }, { req }) {
469+
return initAuth({
470+
commit,
471+
dispatch,
472+
req,
473+
moduleName: 'auth',
474+
cookieName: 'feathers-jwt'
475+
})
476+
}
477+
},
478+
plugins: [
479+
service('courses'),
480+
auth({
481+
state: {
482+
publicPages: [
483+
'login',
484+
'signup'
485+
]
486+
}
487+
})
488+
]
489+
})
490+
}
491+
492+
export default createStore
493+
```
494+
495+
Notice in the above example, I've added a `publicPages` property to the auth state. Let's now use this state to redirect the browser when it's not on a public page and there's no auth:
496+
497+
In your Nuxt project, create the file `/middleware/auth.js`. Then edit the `nuxt.config.js` and add after the `head` property, add a string that references this routing middleware so it looks like this:
498+
499+
*// nuxt.config.js*
500+
```js
501+
router: {
502+
middleware: ['auth']
503+
},
504+
```
505+
506+
Now open the middleware and paste the following content. All it does is redirect the page if there's no auth data in the store.
507+
508+
```js
509+
// If it's a private page and there's no payload, redirect.
510+
export default function (context) {
511+
const { store, redirect, route } = context
512+
const { auth } = store.state
513+
514+
if (!auth.publicPages.includes(route.name) && !auth.payload) {
515+
return redirect('login')
516+
}
517+
}
518+
```
519+
520+
For a summary, the `initAuth` function will make auth available in the state without much configuration.
521+
448522
### Configuration
449523
You can provide a `userService` in the auth plugin's options to automatically populate the user upon successful login.
450524

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"feathers-commons": "^0.8.7",
7272
"feathers-errors": "^2.6.3",
7373
"feathers-query-filters": "^2.1.2",
74+
"jwt-decode": "^2.2.0",
7475
"lodash.clonedeep": "^4.5.0",
7576
"lodash.isobject": "^3.0.2",
7677
"lodash.merge": "^4.6.0",

src/utils.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import _trim from 'lodash.trim'
2+
import decode from 'jwt-decode'
23

34
export function stripSlashes (location) {
45
return Array.isArray(location) ? location.map(l => _trim(l, '/')) : _trim(location, '/')
@@ -26,3 +27,67 @@ export function getNameFromPath (service) {
2627
export const isNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]'
2728

2829
export const isBrowser = !isNode
30+
31+
const authDefaults = {
32+
commit: undefined,
33+
req: undefined,
34+
moduleName: 'auth',
35+
cookieName: 'feathers-jwt'
36+
}
37+
38+
export const initAuth = function initAuth (options) {
39+
const { commit, req, moduleName, cookieName } = Object.assign({}, authDefaults, options)
40+
41+
if (typeof commit !== 'function') {
42+
throw new Error('You must pass the `commit` function in the `initAuth` function options.')
43+
}
44+
if (!req) {
45+
throw new Error('You must pass the `req` object in the `initAuth` function options.')
46+
}
47+
48+
const accessToken = readCookie(req.headers.cookie, cookieName)
49+
const payload = getValidPayloadFromToken(accessToken)
50+
51+
if (payload) {
52+
commit(`${moduleName}/setAccessToken`, accessToken)
53+
commit(`${moduleName}/setPayload`, payload)
54+
}
55+
return Promise.resolve(payload)
56+
}
57+
58+
export function getValidPayloadFromToken (token) {
59+
if (token) {
60+
try {
61+
var payload = decode(token)
62+
return payloadIsValid(payload) ? payload : undefined
63+
} catch (error) {
64+
return undefined
65+
}
66+
}
67+
return undefined
68+
}
69+
70+
// Pass a decoded payload and it will return a boolean based on if it hasn't expired.
71+
export function payloadIsValid (payload) {
72+
return payload && payload.exp * 1000 > new Date().getTime()
73+
}
74+
75+
// Reads and returns the contents of a cookie with the provided name.
76+
export function readCookie (cookies, name) {
77+
if (!cookies) {
78+
console.log('no cookies found')
79+
return undefined
80+
}
81+
var nameEQ = name + '='
82+
var ca = cookies.split(';')
83+
for (var i = 0; i < ca.length; i++) {
84+
var c = ca[i]
85+
while (c.charAt(0) === ' ') {
86+
c = c.substring(1, c.length)
87+
}
88+
if (c.indexOf(nameEQ) === 0) {
89+
return c.substring(nameEQ.length, c.length)
90+
}
91+
}
92+
return null
93+
}

test/index.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import './service-module/getters.test.js'
55
import './service-module/mutations.test.js'
66
import './auth-module/auth-module.test.js'
77
import './auth-module/actions.test.js'
8+
import './utils.test.js'
89

910
import assert from 'chai/chai'
1011
import 'steal-mocha'

test/utils.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import assert from 'chai/chai'
2+
import { initAuth } from '../src/utils'
3+
import feathersNuxt from '../src/index'
4+
import { feathersSocketioClient as feathersClient } from './fixtures/feathers-client'
5+
import Vue from 'vue'
6+
import Vuex from 'vuex'
7+
8+
Vue.use(Vuex)
9+
10+
const { service, auth } = feathersNuxt(feathersClient)
11+
12+
describe('Utils', function () {
13+
it('properly populates auth', function (done) {
14+
const store = new Vuex.Store({
15+
plugins: [
16+
service('todos'),
17+
auth()
18+
]
19+
})
20+
const accessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoiOTk5OTk5OTk5OTkiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.lUlEd3xH-TnlNRbKM3jnDVTNoIg10zgzaS6QyFZE-6g'
21+
const req = {
22+
headers: {
23+
cookie: 'feathers-jwt=' + accessToken
24+
}
25+
}
26+
initAuth({
27+
commit: store.commit,
28+
req,
29+
moduleName: 'auth',
30+
cookieName: 'feathers-jwt'
31+
})
32+
.then(payload => {
33+
assert(store.state.auth.accessToken === accessToken, 'the token was in place')
34+
assert(store.state.auth.payload, 'the payload was set')
35+
done()
36+
})
37+
})
38+
})

0 commit comments

Comments
 (0)