Update auth-routes example

This commit is contained in:
Sébastien Chopin 2016-12-07 17:03:52 +01:00
parent 57997e294e
commit bcaf91a278
6 changed files with 299 additions and 47 deletions

View File

@ -0,0 +1,176 @@
# Authenticated Routes
> Nuxt.js can be used to create authenticated routes easily.
## Using Express and Sessions
To add the sessions feature in our application, we will use `express` and `express-session`, for this, we need to use Nuxt.js programmatically.
First, we install the depedencies:
```bash
yarn add express express-session body-parser whatwg-fetch
```
*We will talk about `whatwg-fetch` later.*
Then we create our `server.js`:
```js
const Nuxt = require('nuxt')
const bodyParser = require('body-parser')
const session = require('express-session')
const app = require('express')()
// Body parser, to access req.body
app.use(bodyParser.json())
// Sessions to create req.session
app.use(session({
secret: 'super-secret-key',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 60000 }
}))
// POST /api/login to log in the user and add him to the req.session.authUser
app.post('/api/login', function (req, res) {
if (req.body.username === 'demo' && req.body.password === 'demo') {
req.session.authUser = { username: 'demo' }
return res.json({ username: 'demo' })
}
res.status(401).json({ error: 'Bad credentials' })
})
// POST /api/logout to log out the user and remove it from the req.session
app.post('/api/logout', function (req, res) {
delete req.session.authUser
res.json({ ok: true })
})
// We instantiate Nuxt.js with the options
new Nuxt({
dev: process.env.NODE_ENV !== 'production'
})
.then((nuxt) => {
app.use(nuxt.render)
app.listen(3000)
console.log('Server is listening on http://localhost:3000')
})
.catch((error) => {
console.error(error)
process.exit(1)
})
```
And we update our `package.json` scripts:
```json
// ...
"scripts": {
"dev": "node server.js",
"build": "nuxt build",
"start": "NODE_ENV=production node server.js"
}
// ...
```
## Using the store
We need a global state to let our application if the user is connected **across the pages**.
To let Nuxt.js use Vuex, we create a `store/index.js` file:
```js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// Polyfill for window.fetch()
require('whatwg-fetch')
const store = new Vuex.Store({
state: {
authUser: null
},
mutations: {
SET_USER: function (state, user) {
state.authUser = user
}
},
actions: {
// ...
}
})
export default store
```
1. We import `Vue` and `Vuex` (included in Nuxt.js) and we tell Vue to use Vuex to let us use `$store` in our components
2. We `require('whatwg-fetch')` to polyfill the `fetch()` method across all browsers (see [fetch repo](https://github.com/github/fetch))
3. We create our `SET_USER` mutation which will set the `state.authUser` to the conntected user
4. We export our store instance to Nuxt.js can inject it to our main application
### nuxtServerInit() action
Nuxt.js will call a specific action called `nuxtServerInit` with the context in argument, so when the app will be loaded, the store will be already filled with some data we can get from the server.
In our `store/index.js`, we can add the `nuxtServerInit` action:
```js
nuxtServerInit ({ commit }, { req }) {
if (req.session && req.session.authUser) {
commit('SET_USER', req.session.authUser)
}
}
```
### login() action
We add a `login` action which will be called from our pages component to log in the user:
```js
login ({ commit }, { username, password }) {
return fetch('/api/login', {
// Send the client cookies to the server
credentials: 'same-origin',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password
})
})
.then((res) => {
if (res.status === 401) {
throw new Error('Bad credentials')
} else {
return res.json()
}
})
.then((authUser) => {
commit('SET_USER', authUser)
})
}
```
### logout() method
```js
logout ({ commit }) {
return fetch('/api/logout', {
// Send the client cookies to the server
credentials: 'same-origin',
method: 'POST'
})
.then(() => {
commit('SET_USER', null)
})
}
```
## Pages components
Then we can use the `$store.state.authUser` data to check if the user is connected in our application.

View File

@ -3,12 +3,14 @@
"description": "", "description": "",
"dependencies": { "dependencies": {
"body-parser": "^1.15.2", "body-parser": "^1.15.2",
"cookie-session": "^2.0.0-alpha.2",
"express": "^4.14.0", "express": "^4.14.0",
"express-session": "^1.14.2",
"nuxt": "latest", "nuxt": "latest",
"whatwg-fetch": "^2.0.1" "whatwg-fetch": "^2.0.1"
}, },
"scripts": { "scripts": {
"start": "node server.js" "dev": "node server.js",
"build": "nuxt build",
"start": "NODE_ENV=production node server.js"
} }
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="container"> <div class="container">
<h1>Please login to see the secret content</h1> <h1>Please login to see the secret content</h1>
<form v-if="!authUser" @submit.prevent="login"> <form v-if="!$store.state.authUser" @submit.prevent="login">
<p class="error" v-if="formError">{{ formError }}</p> <p class="error" v-if="formError">{{ formError }}</p>
<p><i>To login, use <b>demo</b> as username and <b>demo</b> as password.</i></p> <p><i>To login, use <b>demo</b> as username and <b>demo</b> as password.</i></p>
<p>Username: <input type="text" v-model="formUsername" name="username" /></p> <p>Username: <input type="text" v-model="formUsername" name="username" /></p>
@ -9,21 +9,19 @@
<button type="submit">Login</button> <button type="submit">Login</button>
</form> </form>
<div v-else> <div v-else>
Hello {{ authUser.username }}! Hello {{ $store.state.authUser.username }}!
<pre>I am the secret content, I am shown only when the use is connected.</pre> <pre>I am the secret content, I am shown only when the use is connected.</pre>
<p><i>You can also refresh this page, you'll still be connected!</i></p>
<button @click="logout">Logout</button>
</div> </div>
<p><router-link to="/secret">Super secret page</router-link></p>
</div> </div>
</template> </template>
<script> <script>
// Polyfill for window.fetch()
require('whatwg-fetch')
export default { export default {
data ({ req }) { data () {
console.log(req && req.session)
return { return {
authUser: (req && req.session.authUser) || null,
formError: null, formError: null,
formUsername: '', formUsername: '',
formPassword: '' formPassword: ''
@ -31,26 +29,21 @@ export default {
}, },
methods: { methods: {
login () { login () {
fetch('/api/login', { this.$store.dispatch('login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.formUsername, username: this.formUsername,
password: this.formPassword password: this.formPassword
}) })
.then(() => {
this.formUsername = ''
this.formPassword = ''
this.formError = null
}) })
.then((res) => { .catch((e) => {
if (res.status === 401) { this.formError = e.message
this.formError = 'Bad credentials'
} else {
return res.json()
}
})
.then((authUser) => {
this.authUser = authUser
}) })
},
logout () {
this.$store.dispatch('logout')
} }
} }
} }

View File

@ -0,0 +1,18 @@
<template>
<div>
<h1>Super secret page</h1>
<p>If you try to access this URL not conntected, you will be redirected to the home page (server-side or client-side)</p>
<router-link to="/">Back to the home page</router-link>
</div>
</template>
<script>
export default {
// we use fetch() because we do not need to set data to this component
fetch ({ store, redirect }) {
if (!store.state.authUser) {
return redirect('/')
}
}
}
</script>

View File

@ -1,16 +1,17 @@
const Nuxt = require('nuxt') const Nuxt = require('nuxt')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const cookieSession = require('cookie-session') const session = require('express-session')
const app = require('express')() const app = require('express')()
// Body parser, to access req.body // Body parser, to access req.body
app.use(bodyParser.json()) app.use(bodyParser.json())
// Sessions with cookies to have req.session // Sessions to create req.session
app.use(cookieSession({ app.use(session({
name: 'nuxt-session', secret: 'super-secret-key',
keys: ['nuxt-key-1', 'nuxt-key-2'], resave: false,
maxAge: 24 * 60 * 60 * 1000 // 24 hours saveUninitialized: false,
cookie: { maxAge: 60000 }
})) }))
// POST /api/login to log in the user and add him to the req.session.authUser // POST /api/login to log in the user and add him to the req.session.authUser
@ -22,25 +23,18 @@ app.post('/api/login', function (req, res) {
res.status(401).json({ error: 'Bad credentials' }) res.status(401).json({ error: 'Bad credentials' })
}) })
app.use(function (req, res, next) { // POST /api/logout to log out the user and remove it from the req.session
req.session.views = (req.session.views || 0) + 1 app.post('/api/logout', function (req, res) {
next() delete req.session.authUser
res.json({ ok: true })
}) })
app.all('/', function (req, res) { // We instantiate Nuxt.js with the options
console.log(req.session) new Nuxt({
res.send('Hello') dev: process.env.NODE_ENV !== 'production'
}) })
new Nuxt()
.then((nuxt) => { .then((nuxt) => {
app.use(nuxt.render) app.use(nuxt.render)
// app.use(function (req, res) {
// nuxt.render(req, res)
// })
// .then((stream) => {
// stream.pipe(fs.createFile)
// })
app.listen(3000) app.listen(3000)
console.log('Server is listening on http://localhost:3000') console.log('Server is listening on http://localhost:3000')
}) })

View File

@ -0,0 +1,69 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// Polyfill for window.fetch()
require('whatwg-fetch')
const store = new Vuex.Store({
state: {
authUser: null
},
mutations: {
SET_USER: function (state, user) {
state.authUser = user
}
},
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session && req.session.authUser) {
commit('SET_USER', req.session.authUser)
}
},
login ({ commit }, { username, password }) {
return fetch('/api/login', {
// Send the client cookies to the server
credentials: 'same-origin',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password
})
})
.then((res) => {
if (res.status === 401) {
throw new Error('Bad credentials')
} else {
return res.json()
}
})
.then((authUser) => {
commit('SET_USER', authUser)
})
},
logout ({ commit }) {
return fetch('/api/logout', {
// Send the client cookies to the server
credentials: 'same-origin',
method: 'POST'
})
.then(() => {
commit('SET_USER', null)
})
}
}
})
export default store