Created
August 17, 2019 09:52
-
-
Save avamajd/5c451cd35da0290e92ff49a4fda6dd4b to your computer and use it in GitHub Desktop.
Sample of JWT authentication flow implemented using redux-saga and socket.io-client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import http from "http"; | |
let app = require("./server").default; | |
const server = http.createServer(app); | |
const jwt = require("jsonwebtoken"); | |
const User = require("./models/User"); | |
export const io = require("socket.io").listen(server); | |
server.listen(process.env.PORT || 3000, error => { | |
if (error) { | |
console.log(error); | |
} | |
console.log("🚀 started"); | |
}); | |
io.on("connect", socket => { | |
socket.on("authenticate", (data, fn) => { | |
if (data && data.token) { | |
// jwt without Bearer prefix: | |
const token = data.token.split(" ")[1]; | |
jwt.verify(token, secret, (err, decoded) => { | |
if (err) { | |
fn(err); | |
socket.disconnect(true); | |
} else if (decoded) { | |
fn(socket.id); | |
User.findById(decoded.id) | |
.then(user => { | |
if (user) { | |
socket.id = decoded.id; | |
return user; | |
} else return null; | |
}) | |
.catch(err => console.log(err)); | |
} | |
}); | |
} else return new Error("Authentication error"); | |
}); | |
socket.on("disconn", () => { | |
socket.disconnect(true); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { select, call, put, fork, take } from "redux-saga/effects"; | |
import { | |
POPULATE_STATE, | |
LOGIN, | |
LOGIN_COMPLETE, | |
LOGIN_ERROR, | |
LOGOUT, | |
LOGOUT_COMPLETE, | |
TOKEN_EXPIRED | |
} from "./actions/types"; | |
import jwt_decode from "jwt-decode"; | |
import io from "socket.io-client"; | |
const baseUrl = "http://localhost:3000"; | |
function connection(token) { | |
const socket = io(`${baseUrl}`); | |
return new Promise((resolve, reject) => { | |
socket.on("connect", () => { | |
socket.emit("authenticate", { token }, res => { | |
if (res === socket.id) { | |
resolve(socket); | |
} else { | |
reject(res); | |
} | |
}); | |
}); | |
}); | |
} | |
function disconnection(socket) { | |
socket.emit("disconn"); | |
return new Promise((resolve, reject) => { | |
socket.on("disconnect", () => { | |
resolve(socket.disconnected); | |
}); | |
}); | |
} | |
function login(username, password) { | |
let uri = `${baseUrl}/api/users/login`; | |
return fetch(uri, { | |
method: "POST", | |
headers: { | |
Accept: "application/json", | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify({ | |
username, | |
password | |
}) | |
}); | |
} | |
function* loginUser(action) { | |
let errors = {}; | |
try { | |
const { username, password } = action; | |
const response = yield call(login, username, password); | |
const res = yield response.json(); | |
if (res.errors && Object.keys(res.errors).length !== 0) { | |
errors = res.errors; | |
yield put({ type: LOGIN_ERROR, result: errors }); | |
} else { | |
// Decode token to get user data | |
const decoded = yield jwt_decode(res.token); | |
const result = { username: decoded.username, token: res.token }; | |
yield put({ type: LOGIN_COMPLETE, result }); | |
return res.token; | |
} | |
} catch (err) { | |
yield put({ type: LOGIN_ERROR, result: errors }); | |
} | |
} | |
function* populateIt(action) { | |
const token = yield select(state => state.auth.token); | |
return token; | |
} | |
function* authFlow() { | |
while (true) { | |
/* To access token after successful login | |
If already logged in, token wouldn't be empty. */ | |
let action = yield take(POPULATE_STATE); | |
let token = yield call(populateIt, action); | |
if (!token || token === "") { | |
const action = yield take(LOGIN); | |
token = yield call(loginUser, action); | |
} | |
// In case of successful login, token will be returned. | |
if (token !== undefined && token !== "") { | |
try { | |
const socket = yield call(connection, token); | |
if (socket) { | |
yield take(LOGOUT); | |
} | |
// result is expected to be socket.id | |
const result = yield call(disconnection, socket); | |
yield put({ type: LOGOUT_COMPLETE }); | |
// err could be occured due to unsuccessful connection or disconnection | |
} catch (err) { | |
// action: TOKEN_EXPIRED | |
yield put({ type: TOKEN_EXPIRED }); | |
} | |
} | |
} | |
} | |
const mySaga = function* mySaga() { | |
yield fork(authFlow); | |
}; | |
export default mySaga; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment