Skip to content

Instantly share code, notes, and snippets.

@KidkArolis
Created November 20, 2019 17:24
Show Gist options
  • Save KidkArolis/4cd2dfbeb7f7e6447a5017ffd76c2c5b to your computer and use it in GitHub Desktop.
Save KidkArolis/4cd2dfbeb7f7e6447a5017ffd76c2c5b to your computer and use it in GitHub Desktop.
Feathers Client with robust reauthentication
import createFeathersClient from '@feathersjs/feathers'
import auth from '@feathersjs/authentication-client'
import socketio from '@feathersjs/socketio-client'
import io from 'socket.io-client'
export function createFeathers() {
const socket = io()
const feathers = createFeathersClient()
feathers.configure(socketio(socket))
feathers.configure(
auth({
storage: window.localStorage,
storageKey: 'access-token',
path: '/api/authentication',
Authentication: AuthenticationClient,
}),
)
feathers.hooks({
before: {
all: [ensureConnected(), markConnected()],
},
error: {
all: [handleUnauthenticated()],
},
})
return feathers
}
class AuthenticationClient extends auth.AuthenticationClient {
handleSocket(socket) {
// Connection events happen on every reconnect
const connected = this.app.io ? 'connect' : 'open'
socket.on(connected, () => {
this.connected = true
// Only reconnect when `reAuthenticate()` or `authenticate()`
// has been called explicitly first
if (this.authenticated) {
// Force reauthentication with the server
this.reAuthenticate(true)
.then(() => {
while ((this.waiting || []).length) {
this.waiting.pop()()
}
})
.catch(err => {
if (err.code === 401) {
// failed to reauthenticate a socket in the background,
// let's reload the page so that we're taken to /login
if (window.location.pathname !== '/login') {
window.location.reload()
}
}
})
}
})
socket.on('disconnect', () => {
this.connected = false
})
}
isConnected() {
if (this.connected) {
return Promise.resolve()
} else {
this.waiting = this.waiting || []
return new Promise(resolve => {
this.waiting.push(resolve)
})
}
}
handleError(error, type) {
if (error.code === 401 || error.code === 404) {
const promise = this.removeAccessToken().then(() => this.reset())
return type === 'logout'
? promise
: promise.then(() => Promise.reject(error))
}
return Promise.reject(error)
}
}
function ensureConnected() {
return async context => {
const authServicePath =
context.app.authentication.options.path || 'authentication'
if (context.path !== authServicePath) {
await context.app.authentication.isConnected()
}
}
}
function markConnected() {
return context => {
const { app } = context
const { authentication } = app
context.params.connected = authentication.connected
}
}
function handleUnauthenticated() {
return context => {
const { params, error } = context
// The socket is connected, but server is responding with
// 401s, that means the token is expired, invalid, etc.
// The other case is when the socket is not connected, then we
// do not want to reload the page, we just want to wait for the socket
// to reconnect and reauthenticate, this is typically due to temporary
// loss of server connection
if (error.name === 'NotAuthenticated' && params.connected) {
if (window.location.pathname !== '/login') {
window.location.reload()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment