I am using the setInterval function to do an API call to get refresh token after some interval. Every time if I refresh the browser setInterval timer gets clear and starts counting from zero. My access token gets expired and the refresh token never getting a call and user logged out. Is there any way to deal with this?
useEffect(() => {
const interval = setInterval(() => {
setTokenByReSendingAuth(); //dispatch action
}, 300000);
return () => clearInterval(interval);
}, [setTokenByReSendingAuth]);
Using MERN:
Heres your dependencies:
jwt-decode: https://www.npmjs.com/package/jwt-decode
passport-jwt: http://www.passportjs.org/packages/passport-jwt/
passport: https://www.npmjs.com/package/passport
jsonwebtoken: https://www.npmjs.com/package/jsonwebtoken
bcryptjs: https://www.npmjs.com/package/bcryptjs
Create an express server.js like this:
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const path = require("path");
const passport = require("passport");
const db = require("./config/.env").mongoURI; //uri in .env file
const app = express();
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
mongoose.connect(db, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
});
const connection = mongoose.connection;
connection.once("open", () => {
console.log("MongoDB database connection established successfully");
});
const myRouter = require("./routes/example.js");
app.use(passport.initialize()); // used to attatch token to request headers
require("./config/passport")(passport);
app.use("/example", myRouter);
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
});
}
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
Install your dependencies:
"dependencies": {
"axios": "^0.21.1",
"bcryptjs": "^2.4.3",
"concurrently": "^5.3.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.11.8",
"passport": "^0.4.1",
"passport-jwt": "^4.0.0",
"validator": "^13.5.2",
"nodemon": "^2.0.7"
},
Add this to your package.json in whatever directory your server is in:
"devDependencies": {
"nodemon": "^2.0.7"
},
"scripts": {
"start": "node server.js",
"server": "nodemon server.js"
},
Now to the Auth part:
Make a config folder for your passport and URI:
In a .env file:
module.exports = {
mongoURI: "mongodb+srv://",
secretOrKey: "abunchofrandomcharacterscreatedwithbcrypt",
};
Make a passport.js file:
This adds the user's token to all request headers, it is automatically running since we used it in our server.js file.
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
const mongoose = require("mongoose");
const User = mongoose.model("users");
const keys = require("./.env");
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = keys.secretOrKey;
module.exports = (passport) => {
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id)
.then((user) => {
if (user) {
return done(null, user);
}
return done(null, false);
})
.catch((err) => console.log(err));
})
);
};
Make a middleware folder for your backend:
Add an auth.js file:
const jwt = require("jsonwebtoken");
const config = require("../config/.env").secretOrKey;
function authUser(req, res, next) {
const authHeader = req.header("Authorization");
const token = authHeader && authHeader.split(" ")[1];
// Check for token
if (!token)
return res.status(401).json({ msg: "No token, authorization denied" });
try {
// Verify token
const decoded = jwt.verify(token, config);
// Add user from payload
req.user = decoded;
next();
} catch (e) {
res.status(400).json({ msg: "Token is not valid" });
}
}
module.exports = {
authUser,
};
This file is attached to your routes in the header, like this:
router.post("/example/get", authUser, (req, res) => {
const { reqData } = req.body; //dont ever put user ids in here
.catch((err) => {
res.status(400).json({ msg: err });
});
});
The route to login and register should look like this:
const router = require("express").Router();
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
const { authUser } = require("../middleware/auth"); //used in the header of auth needed requests
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const keys = require("../config/.env");
const validateRegisterInput = require("../validation/register"); //checks and validates user register inputs
const validateLoginInput = require("../validation/login"); //checks and validates user register inputs
const User = require("../models/user");
const { Permissions } = require("../models/permissions");
//uses a middleware to validate register inputs, checks if user data exists in db, salts and hashes the password.
router.post("/register", (req, res) => {
const { errors, isValid } = validateRegisterInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
User.findOne({ email: req.body.email }).then((user) => {
if (user) {
return res.status(400).json({ email: "Email already exists" });
} else {
const newUser = new User({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: req.body.password,
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then((user) => res.json(user))
.catch((err) => console.log(err));
});
});
}
});
});
//login creds are req through this route, the details are compared to the db user collection, and the user data that matches the decoded password and username will be responed back through the token.
router.post("/login", (req, res) => {
const { errors, isValid } = validateLoginInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
const email = req.body.email;
const password = req.body.password;
User.findOne({ email }).then((user) => {
if (!user) {
return res.status(404).json({ email: "Email not found" });
}
bcrypt.compare(password, user.password).then((isMatch) => {
if (isMatch) {
const payload = {
id: user.id,
firstName: user.firstName,
};
jwt.sign(
payload,
keys.secretOrKey,
{
expiresIn: 31556926, //expires in a year
},
(err, token) => {
res.json({
success: true,
token: "Bearer " + token,
});
}
);
} else {
return res
.status(400)
.json({ passwordincorrect: "Password incorrect" });
}
});
});
});
module.exports = router;
That's basically it for the backend auth routing side of things, but for the client to get there token in the browser you need to add this stuff to the client:
In your index.js add this outside a component to run on every render no matter what:
This checks to see if there's a jwttoken in the browser, it decodes it and sets the user data into the state to be used globally. It also redirects the user.
import setAuthToken from "./utils/setAuthToken";
import jwt_decode from "jwt-decode";
if (localStorage.jwtToken) {
// Set auth token header auth
const token = localStorage.jwtToken;
setAuthToken(token);
// Decode token and get user info and exp
const decoded = jwt_decode(token);
// Set user and isAuthenticated
store.dispatch(setCurrentUser(decoded)); // using redux, can easily also just use contextApi or something else
// Check for expired token
const currentTime = Date.now() / 1000; // to get in milliseconds
if (decoded.exp < currentTime) {
// Logout user
store.dispatch(logoutUser());
// Redirect to login
window.location.href = "./";
}
}
Create login, register and logout functions:
import axios from "axios";
import setAuthToken from "../../utils/setAuthToken";
import jwt_decode from "jwt-decode";
import { SET_CURRENT_USER } from "./authTypes"; //puts user data into state
import { showSnackbar } from "../inventory/inventoryActions";
export const registerUser = (userData) => (dispatch) => {
axios
.post("/users/register", userData)
.then(() => {
console.log("logged in")
})
.catch(() => {
console.log("something wrong")
});
};
export const loginUser = (userData) => (dispatch) => {
axios
.post("/users/login", userData)
.then((res) => {
const { token } = res.data;
localStorage.setItem("jwtToken", token);
setAuthToken(token);
const decoded = jwt_decode(token);
dispatch(setCurrentUser(decoded));
dispatch(showSnackbar(`Successfully signed in!`, "success", 3000));
})
.catch(() => {
console.log("somethings wrong")
});
};
export const setCurrentUser = (decoded) => { // used in loginUser
return {
type: SET_CURRENT_USER,
payload: decoded,
};
};
//removes token from localstorage
export const logoutUser = () => {
return (dispatch) => {
localStorage.removeItem("jwtToken");
setAuthToken(false);
dispatch(setCurrentUser({}));
};
};
If you have any private components you only want logged in users to access use this PrivateRoute Component wrapper:
This redirects any user not logged in to the home page
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
<Route
{...rest}
render={(props) =>
auth.isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect to="/" />
)
}
/>
);
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
});
export default connect(mapStateToProps)(PrivateRoute);
Use it as a react-router-dom element:
<PrivateRoute exact path="/example" component={privateComponentExample} />
If you have any questions, let me know. :)
Related
I have a very simple login system, which takes a username and a password from a user, and when the form is submitted the data is posted to an express http app.
The login function at the react app is as following:
async function login(event){
event.preventDefault();
setRequestState(REQUEST_STATE.pending)
const request = `${SERVER_URL}/login`;
const formData = new FormData();
formData.append("username",data["username"])
formData.append("password",data["password"])
const response = await fetch(request, {
method: 'POST',
body: formData,
enctype:"multipart/form-data",
credentials: 'same-origin',
})
if(!response.ok){
if(response.status === 401){
console.log("username or password are inccorect")
setDisplayError(response.status)
}
if(response.status === 500){
console.log("internal server error")
setDisplayError(response.status)
}
setRequestState(REQUEST_STATE.no_request)
return;
}
setRequestState(REQUEST_STATE.success)
// navigate("/dashboard")
}
the response is successful consistently when the right data is entered.
I use express-sessions to handle sessions, the setup code for the sessions is as following:
const express = require('express');
const cors = require('cors')
const loginRouter = require('./src/routes/login.js');
const session = require('express-session')
const app = express()
app.use(
cors({
origin:true,
credentials:'same-origin',
}))
app.use(session({
secret: 'test',
resave: false,
saveUninitialized: false,
// store: sessionStore,
cookie: {
secure: false,
sameSite: 'none',
}
}));
app.use(express.urlencoded({extended: true}))
app.use('/login', loginRouter)
app.listen(process.env.PORT ,()=>{
console.log(`Server is listening on port ${process.env.PORT}`)
})
login router is as following:
const express = require('express')
const multer = require('multer')
const router = express.Router()
const {getLogin,postLogin, authenticateLogin} = require('../controllers/login')
upload = multer({})
router.post("/", upload.array(0), authenticateLogin, postLogin)
router.get("/", getLogin)
module.exports=router
login controller is as following:
const {pool} = require('../../sqlconfig')
const bcrypt = require('bcrypt');
const postLogin = (req, res) => {
return res.status(200).send({success:true});
}
const getLogin = (req, res) => {
// console.log("get this login betch")
console.log(req.session.id)
if(req.session.userId){
return res.status(200).send({success:true});
}
// console.log("couldn't get login")
return res.status(401).send({success:false});
}
const authenticateLogin = async (req, res, next) => {
const dict = JSON.parse(JSON.stringify(req.body));
const username = dict["username"];
const password = dict["password"];
if(!dict || !username || !password) {
// console.log("Access deneid")
return res.status(401).send({error:"Access denied"});
}
try{
const query = "SELECT passwrd FROM admins WHERE admins.username=?";
const [result] = await pool.execute(query,[username])
if(result.length <= 0){
return res.status(401).send({error:"Access denied"});
}
const passwordInDB = result[0].passwrd;
if(!passwordInDB){
return res.status(401).send({error:"Access denied"});
}
const match = await bcrypt.compare(password, passwordInDB);
// console.log(match)
if(!match){
return res.status(401).send({error:"Access denied"});
}
const queryUserId = "SELECT id FROM admins WHERE admins.username=?";
const [resultUserId] = await pool.execute(queryUserId,[username])
req.session.userId = resultUserId[0].id;
console.log("logged in successfuly!")
console.log(req.session)
next()
}
catch(e){
console.log(e)
}
}
module.exports={
authenticateLogin,
postLogin,
getLogin
}
when login post is called, a session does get created,
and a cookie is sent back to the react application:
Response
but doesn't get saved:
saved cookies
I am trying to use blitz.js login API in a Flutter project. So I have created a /api/auth/login.ts file with the following code
import { getAntiCSRFToken, getSession, SecurePassword } from "#blitzjs/auth"
import { authenticateUser } from "app/auth/mutations/login"
import { AuthenticationError } from "blitz"
import db from "db"
import { Role } from "types"
const handler = async (req, res) => {
const session = await getSession(req, res)
const { email, password } = req.body
if (req.method !== "POST" || !req.body.data || !session.userId) {
res.status(401).json({ error: `Do not tamper with this route!` })
} else {
console.log("Create a new session for the user")
// Create a new session for the user
//login
const user = await authenticateUser(email, password)
const user = await db.user.findFirst({ where: { email } })
if (!user) return res.json({ data: "Hello", email, password })
const result = await SecurePassword.verify(user.hashedPassword, password)
const { hashedPassword, ...rest } = user
await req.session.$create({ userId: user.id, role: user.role as Role })
res.json({ rest })
}
export default handler
I also tried to use their docs but it was not clear enough and understandable
Can I use ctx.session.$create and insert it to db using blitz.js api
I have solved the problem using this code
import { Role } from "types"
import { authenticateUser } from "app/auth/mutations/login"
import { getSession } from "#blitzjs/auth"
export default async function customRoute(req, res) {
const session = await getSession(req, res)
const { email, password } = req.body
console.log(email, password)
console.log(session.$isAuthorized())
const user = await authenticateUser(email, password)
if (user.id === session.userId) {
return res.status(409).json({ error: `Already exist` })
}
await session.$create({ userId: user.id, role: user.role as Role })
// // res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({ userId: session.userId }))
}
At first, I was getting a CSRF mismatch error and then a localStorage is undefined and now somehow everything is working with this code.
I am trying to refresh the authentication token when it is near the expiration time, however I end up with an endless loop. The expectation is that the code that checks the expiration time in App.js should work and reset the auth token and the refresh token, however is starts looping endlessly.
I have the following Auth helper functions:
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const { AuthenticationError } = require("apollo-server-express");
const config = require("config");
const jwtSecret = config.get("jwt_secret");
const jwtRefreshTokenSecret = config.get("jwt_refresh_token");
const authFunctions = {
checkSignedIn: async (req, requireAuth = true) => {
const header = req.headers.authorization;
if (header) {
const token = header.replace("Bearer ", "");
const decoded = jwt.verify(token, jwtSecret);
console.log(decoded);
let user = await User.findById(decoded.id);
if (!user) {
throw new AuthenticationError("Invalid user credentials.");
}
return user;
}
if (requireAuth) {
throw new AuthenticationError("You must be logged in.");
}
return null;
},
issueToken: async (user) => {
let token = "Bearer " + (await authFunctions.createToken(user));
let refreshToken = await authFunctions.createToken(user, 100);
return { token, refreshToken };
},
issueNewToken: async (req) => {
try {
const token = req.headers.refreshtoken;
if (token) {
const decoded = await jwt.verify(token, jwtRefreshTokenSecret);
let user = await User.findById(decoded.id);
console.log(user);
if (!user) {
throw new AuthenticationError("No user found.");
}
let tokens = await authFunctions.issueToken(user);
return { ...tokens, user };
}
} catch (err) {
throw new AuthenticationError("Invalid Refresh Token.");
}
},
createToken: async ({ id, address }, expiresIn = 60) => {
let secret = expiresIn === 60 ? jwtSecret : jwtRefreshTokenSecret;
return await jwt.sign({ id, address }, secret, { expiresIn });
},
};
module.exports = authFunctions;
The schema:
const { gql } = require("apollo-server");
const typeDefs = gql`
scalar Date
scalar MongoId
type User {
_id: MongoId!
address: String!
createdAt: Date
}
type Auth {
user: User!
token: String!
refreshToken: String!
}
input LoginInput {
address: String!
}
type Query {
refreshTokens: Auth!
}
type Mutation {
createOrGetUser(loginInput: LoginInput): Auth!
}
`;
module.exports = typeDefs;
The resolvers:
const User = require("../../models/User");
const {
issueToken,
issueNewToken,
checkSignedIn,
} = require("../../middleware/Auth");
const resolvers = {
Query: {
refreshTokens: async (root, args, { req }, info) =>
await issueNewToken(req),
},
Mutation: {
createOrGetUser: async (root, args, { req }, info) => {
try {
const existingUser = await User.findOne({
address: args.loginInput.address,
})
.populate({
path: "orders",
model: Order,
})
.exec();
if (existingUser) {
let tokens = await issueToken(existingUser);
return {
user: existingUser,
...tokens,
};
}
const user = await new User({
address: args.loginInput.address,
});
const result = await user.save();
let tokens = await issueToken(result);
return {
user,
...tokens,
};
} catch (err) {
throw err;
}
},
},
};
module.exports = resolvers;
And the App.js
import React, { Fragment } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import {
ApolloClient,
ApolloLink,
createHttpLink,
useReactiveVar,
concat,
} from "#apollo/client";
import { ApolloProvider } from "#apollo/client/react";
import { accessTokenVar, isLoggedInVar } from "./cache";
import cache from "./cache.js";
import jwtDecode from "jwt-decode";
// Styling
import "./styles/App.css";
// Components
import Routes from "./components/routing/Routes";
import Navbar from "./components/navbar/Navbar";
import { REFRESH_TOKENS } from "./queries/User";
const httpLink = createHttpLink({
uri: "/graphql",
});
const refreshTokens = () => {
return client.query({ query: REFRESH_TOKENS }).then((response) => {
console.log(response);
// Need to set the token to cache here, but the query doesn't get executed it seems
//accessTokenVar(response.data)
return;
});
};
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
let token = accessTokenVar();
if (token) {
token = token.replace("Bearer ", "");
const { exp } = jwtDecode(token);
console.log(exp);
// Refresh the token a minute early to avoid latency issues
const expirationTime = exp - 30;
if (Date.now() / 1000 >= expirationTime) {
refreshTokens(); // this keeps going on a loop
}
}
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
authorization: token ? token : "",
},
}));
return forward(operation);
});
const client = new ApolloClient({
cache,
link: concat(authMiddleware, httpLink),
connectToDevTools: true,
credentials: "include",
});
const App = () => {
const accessToken = useReactiveVar(accessTokenVar);
const isLoggedIn = useReactiveVar(isLoggedInVar);
//let isLoggedIn;
accessToken ? isLoggedInVar(true) : isLoggedInVar(false);
return (
<ApolloProvider client={client}>
<Router>
<Fragment>
<Navbar isLoggedIn={isLoggedIn} />
</Fragment>
</Router>
</ApolloProvider>
);
};
export default App;
Note: When createOrGetUser is executed the first time, the flow works. Then, I wait for 30 seconds and send a protected query, after which it gets in an endless loop.
Overall, I feel this flow is broken somehow, but I can't figure out what exactly. Would appreciate any help with this!
I have a createUser function that sends an axios req to express and takes some time to finish creating a firebase authenticated user, create a token and send token back to user as a cookie.
I also have a getLoggedIn function that takes the cookie and sends it to express to get authenticated and then adjust my authContext accordingly.
My function looks like this: await createUser(registerData ).then(() => getLoggedIn())
My problem is that getLoggedIn() is being called early, before the createUser is done sending back the cookie. Any idea how to fix this? Is there a way to listen for a cookie change? I dont see other people doing that, do I have the wrong approach in general?
Thanks for any help.
New User Form
const { getLoggedIn, loggedIn } = useContext(AuthContext);
const register = async (e) => {
e.preventDefault();
const registerData = {
email,
password,
passwordVerify,
};
try {
await createUser(registerData).then(() => getLoggedIn());
} catch (err) {
console.log(err);
}
}};
CreateUser
export const createUser = async (props) => {
console.log("New User Creation Initiated", props);
const { email, password, passwordVerify } = props;
let userObject = { tokenId: null };
try {
await firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
console.log("createUserWithEmailAndPassword", user);
user
.getIdToken()
.then((tokenId) => {
userObject = { tokenId: tokenId };
axios.post(`${domain}/auth`, userObject);
})
.catch((err) => console.log("new user error", err));
})
.catch((err) => {
console.log("createUser Error", err);
});
return;
} catch (err) {
console.log("New User Creation Error: ", err);
}
};
AuthContext
const AuthContextProvider = (props) => {
console.log("AuthContextProvider Initiated");
const [loggedIn, setLoggedIn] = useState(null);
const getLoggedIn = async () => {
console.log("getLoggedIn Initiated");
let validatedUser = await axios.get(`${domain}/auth/loggedIn`);
console.log("AuthContextProvider validatedUser", validatedUser);
setLoggedIn(validatedUser.data);
};
useEffect(() => {
getLoggedIn();
}, []);
return (
<AuthContext.Provider value={{ loggedIn, getLoggedIn }}>
{props.children}
</AuthContext.Provider>
);
};
Express
router.post("/", async (req, res) => {
console.log("---User signup initiated---");
try {
const { tokenId } = req.body;
console.log("tokenId passed from frontend: ", tokenId);
admin
.auth()
.verifyIdToken(tokenId)
.then((decodedToken) => {
const { email, uid } = decodedToken;
console.log("Fetched UID: ", uid);
console.log("Fetched email: ", email);
const Users = db.collection("users");
Users.doc(`${uid}`).set({
email: email,
posts: [],
});
console.log("---jwt signing initiated---");
const token = jwt.sign(
{
user: { email, uid },
},
process.env.JWT_SECRET
);
console.log("token log: ", token);
return res
.cookie("token", token, {
httpOnly: true,
sameSite: "none",
secure: true,
})
.send();
});
} catch (err) {
console.log(err);
}
});
router.get("/loggedIn", (req, res) => {
console.log("login validation initiated");
try {
const token = req.cookies.token;
if (!token) {
console.log("no token cookie");
return res.json(null);
}
const validatedUser = jwt.verify(token, process.env.JWT_SECRET);
console.log("Token cookie: ", validatedUser);
res.json(validatedUser);
} catch (err) {
console.log("loggedIn", err);
}
});
You can use onAuthStateChanged like this in your AuthContext.
const AuthContextProvider = (props) => {
console.log("AuthContextProvider Initiated");
const [loggedIn, setLoggedIn] = useState(null);
const getLoggedIn = async () => {
console.log("getLoggedIn Initiated");
let validatedUser = await axios.get(`${domain}/auth/loggedIn`);
console.log("AuthContextProvider validatedUser", validatedUser);
setLoggedIn(validatedUser.data);
};
const handleAuthStateChanged = user => {
if(user) { //user login
getLoggedIn()
} else { //user logout
setLoggedIn(null)
}
}
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
handleAuthStateChanged(user)
})
return unsubscribe
}, []);
return (
<AuthContext.Provider value={{ loggedIn, getLoggedIn }}>
{props.children}
</AuthContext.Provider>
);
};
In my createUser function I moved my axios call and returned it outside of the firebase.auth() sequence of promises. Seems to work now.
export const createUser = async (props) => {
console.log("New User Creation Initiated", props);
const { email, password, passwordVerify } = props;
let userObject = { tokenId: null };
try {
await firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
console.log("createUserWithEmailAndPassword", user);
user
.getIdToken()
.then((tokenId) => {
userObject = { tokenId: tokenId };
})
.catch((err) => console.log("new user error", err));
})
.catch((err) => {
console.log("createUser Error", err);
});
return axios.post(`${domain}/auth`, userObject);
} catch (err) {
console.log("New User Creation Error: ", err);
}
};
I am trying to implement Google OAuth with MERN Stack.
There was a nice blog about this like as below.
https://medium.com/#alexanderleon/implement-social-authentication-with-react-restful-api-9b44f4714fa
I used react-google-login for the front-end and passport, passport-google-token, and jsonwebtoken for the back-end.
It was all good until express js got the accessToken and created a User document, but I actually don't know what is the next step to keep login and authenticate it. This question is mentioned in the above article, but he hasn't implemented it.
Is there any information to solve this?
"Step 7: Return a JWT token to the frontend. What you do with that token is out of scope for this tutorial, but it should probably be used to authenticate each of the logged-in user’s actions."
My react side code is as below
import React from 'react';
import GoogleLogin from 'react-google-login';
import api from '../config/api';
import { useGlobalState } from '../config/globalState';
import { AUTH_SIGN_IN, SET_USER } from '../config/types';
const Navbar = () => {
const { state, dispatch } = useGlobalState();
const { isLoggedIn } = state;
const responseGoogle = async (data) => {
try {
const { accessToken } = data;
const res = await api.post('/api/auth/signin', {
access_token: accessToken
});
console.log('res.data:', res.data);
dispatch({ type: AUTH_SIGN_IN });
} catch (err) {
console.log('err: ', err.message);
}
};
return (
<div>
This is the title
{
!isLoggedIn &&
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Login with Google"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
}
</div>
)
}
export default Navbar
This is passport.js
const passport = require('passport');
const GooglePlusTokenStrategy = require('passport-google-token').Strategy;
const User = require('../models/user');
module.exports = passport => {
passport.use('googleToken', new GooglePlusTokenStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}, async (accessToken, refreshToken, profile, done) => {
console.log('accessToken:', accessToken);
console.log('refreshToken:', refreshToken);
console.log('profile:', profile);
try {
const foundUser = await User.findOne({ googleId: profile.id})
if (foundUser) {
console.log('User already exists');
return done(null, foundUser);
} else {
const newUser = await User.create({
googleId: profile.id,
displayName: profile.displayName,
email: profile.emails[0].value,
photo: profile.photos[0].value
});
await newUser.save();
return done(null, newUser);
}
} catch(error) {
done(error, false, error.message);
}
}));
}
This is the /api/auth/signin route handling part.
const JWT = require('jsonwebtoken');
const User = require('../models/user');
const signToken = user => {
return JWT.sign({
iss: 'AnySecret',
sub: user.id,
iat: new Date().getTime(),
exp: new Date().setDate(new Date().getDate() + 1)
}, process.env.JWT_SECRET);
}
module.exports = {
signIn: async (req, res) => {
console.log('signIn called');
const token = signToken(req.user);
res.cookie('access_token', token, {
httpOnly: true
});
res.status(200).send({
user: req.user,
token
});
}
}