Skip to content

Instantly share code, notes, and snippets.

@avamajd
Created August 17, 2019 09:52
Show Gist options
  • Save avamajd/5c451cd35da0290e92ff49a4fda6dd4b to your computer and use it in GitHub Desktop.
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
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);
});
});
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