I am using NodeJS ReactJS Redux with JWT in an application and I have issue getting expiresIn and token at the reactjs frontend. When I console log the token at the NodeJS controller, it displays the token nd everything perfectly but it pointed to the jwt.verify() with error JsonWebTokenError: jwt malformed but at the ReactJS frontend, it displays the payLoad userData part of the token and displays undefined for both expiresIn and token.
As you can see in the Redux authSlice class that I set localStorage Item in there for token, expire and userData but when I tried to get the localStorage item in another page, I could only get the userData payloads but the token and expire are undefined.
I don't know what is wrong here because the NodeJS sent the token for real as I can get the token from console of NodeJS and ThunderClient API as well gave an 200 OK when i test the api using ThunderClient in VS Code.
My concern is that ThunderClient displays 200 OK and return Token, expiresIn and userData complete and everything perfect, NodeJS console displays correct information on console but gave JsonWebTokenError: jwt malformed and at ReactJS frontend, I got the userData from the token sent by the API but accessToken and expiresIn are missing i.e out of the 3 string that JWT encrypted, I got only the Payload which is the userData.
How can I solve this?
*******************NODEJS
jwtHelper.js
exports.extractToken = (req) => {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
jwtVerify.js
module.exports = function (req, res, next) {
try {
const token = extractToken(req);
if (token == null) {
return res.status(StatusCodes.UNAUTHORIZED).send("Unauthorized");
}
jwt.verify(token, common_helper.getJWTAccessToken(), {}, (err, user) => {
if (err) {
console.log(err);
return res.status(StatusCodes.FORBIDDEN).send("Invalid user");
}
req.user = user["userData"];
next();
});
} catch (e) {
next(e);
}
};
Login (Controller)
const token = jwt.sign({userData}, common_helper.getJWTAccessToken(), {
algorithm: 'HS256',
expiresIn: common_helper.getJWTExpiryTime(),
});
res.status(StatusCodes.OK).send({"expires_in": common_helper.getJWTExpiryTime(),"access_token":token,"token_type": "bearer","userData":userData});
console.log(`The token is ${token}`) // This displays the ciper token
console.log(`The secret_token is ${common_helper.getJWTExpiryTime()}`) //this displays the real secret key
*******************REACTJS
Redux Slice. Note that the localStorage is set here
import { createSlice, PayloadAction } from "#reduxjs/toolkit";
interface IAuthToken {
isAuthenticated?:boolean,
jwtToken: any;
expiryDate: any;
errorMessage?:string;
userData?:any;
notverified?: string;
}
const initialState: IAuthToken = {
jwtToken: undefined,
expiryDate: undefined,
errorMessage:'',
isAuthenticated:false,
userData:undefined,
notverified: undefined,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setJWTToken: (state, _action: PayloadAction<IAuthToken>) => {
state.jwtToken = _action.payload.jwtToken;
state.expiryDate = _action.payload.expiryDate;
state.userData=_action.payload.userData;
localStorage.setItem('token', state.jwtToken);
//localStorage.setItem('token', JSON.stringify(state.jwtToken));
localStorage.setItem('expire', state.expiryDate);
//localStorage.setItem('expire', JSON.stringify(state.expiryDate));
if(state.userData)
localStorage.setItem('userData',JSON.stringify(state.userData));
state.isAuthenticated = true;
state.notverified = _action.payload.notverified;
},
removeJWTToken: (state) => {
localStorage.clear();
state.jwtToken = '';
state.expiryDate=undefined;
state.isAuthenticated = false;
},
setError: (state, _action: PayloadAction<string>) => {
state.errorMessage = _action.payload;
},
},
});
export const { setJWTToken, removeJWTToken,setError } = authSlice.actions;
export default authSlice.reducer;
ReactJS Login
Axios.post(`${baseURL}/signin`, { username: formik.values.username, password: formik.values.password})
.then((response) => {
if(response.data.notverified)
{
setSubmitting("");
navigate("/needemailconfirmation", { replace: true });
}
setSubmitting("");
console.log(response.data)
dispatch(setJWTToken(response.data));
navigate("/dashboardd", { replace: true });
authAction
export const signIn = (email, password) => {
return (dispatch) => {
axios
.post(`${url}/signin`, { email, password })
.then((token) => {
localStorage.setItem("token", token.data);
dispatch({
type: "SIGN_IN",
token: token.data,
});
})
.catch((error) => {
console.log(error.response);
toast.error(error.response?.data, {
position: toast.POSITION.BOTTOM_RIGHT,
});
});
};
};
authReducer
const authReducer = (state = initialState, action) => {
switch (action.type) {
case "SIGN_IN":
case "SIGN_UP":
case "USER_LOADED":
toast("Welcome...", {
position: toast.POSITION.BOTTOM_RIGHT,
});
const user = jwtDecode(action.token);
return {
...initialState,
token: action.token,
name: user.name,
email: user.email,
_id: user._id,
};
case "SIGN_OUT":
localStorage.removeItem("token");
toast("Goodbye...", {
position: toast.POSITION.BOTTOM_RIGHT,
});
return {
token: null,
name: null,
email: null,
_id: null,
};
default:
return state;
}
};
In my ReactJS frontend, from the token sent by the NodeJS api I was able to get userData payload from the token but I could not get the token and expiresIn for reason I do not know.
You can find the token details from the browser inspect here where I got userData payload but got undefines for accesstoken and expiresIn
Update
In my question above, I made the point of not getting the token value in my ReactJS frontendm while debugging the code I set the localStorage at the login page immediately after a successful login and i used the localStorage getItem() to get the value in my Redux slice but the issue that persist is that my ReactJS frontend is not allowing me to get to dashboard as it is acting like the value is not set i.e the middleware cannot get the value at the and the route allow the user to navigate to the dashboard is bouncing the navigation back to login page despite the fact that the token is set and I can get the token at the browser inspect to be sure that it is set but Auth Middleware is not getting it. See the code I have now below:
******************ReactJS
Login.js
//call the api
Axios.post(`${baseURL}/signin`, { username: formik.values.username, password: formik.values.password})
.then((response) => {
if(response.data.notverified)
{
setSubmitting("");
navigate("/needemailconfirmation", { replace: true });
}
setSubmitting("");
console.log(response.data)
if(response.data.access_token){
dispatch(setJWTToken(response.data));
localStorage.setItem('token', JSON.stringify(response.data.access_token));
localStorage.setItem('expire', JSON.stringify(response.data.expires_in));
localStorage.setItem('userData', JSON.stringify(response.data.userData));
}
navigate("/dashboardd", { replace: true });
}) .catch((error) =>{
setSubmitting("");
//console.log(error.data);
setErrorMsg("Invalid Login Credentials Supplied.");
//alert("Invalid Login Credentials Supplied, Error : <br>" + error.data);
});
}
Redux Slice Auth File
interface IAuthToken {
isAuthenticated?:boolean,
jwtToken: any;
expiryDate: any;
errorMessage?:string;
userData?:any;
notverified?: string;
}
const initialState: IAuthToken = {
jwtToken: undefined,
expiryDate: undefined,
errorMessage:'',
isAuthenticated:false,
userData:undefined,
notverified: undefined,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setJWTToken: (state, _action: PayloadAction<IAuthToken>) => { state.jwtToken = localStorage.getItem("token");
state.expiryDate = localStorage.getItem("expire");
state.userData = localStorage.getItem("userData");
if(localStorage.getItem("userData"))
state.isAuthenticated = true;
state.notverified = '';
},
removeJWTToken: (state) => {
localStorage.clear();
state.jwtToken = '';
state.expiryDate=undefined;
state.isAuthenticated = false;
},
setError: (state, _action: PayloadAction<string>) => {
state.errorMessage = _action.payload;
},
},
});
export const { setJWTToken, removeJWTToken,setError } = authSlice.actions;
export default authSlice.reducer;
AuthenticatedRoute.js : this is used in bouncing user back if they token is not found
const AuthenticatedRoute: FC<{ element: any }> = ({ element }) => {
const token = localStorage.getItem('token');
const expire = localStorage.getItem('expire');
const now = Date.now();
if (token && expire && parseInt(expire) > parseInt(now.toString())) {
return element;
} else {
return <Navigate to="/" replace />
}
};
export default AuthenticatedRoute;
My React Router Dom
<Route path="/checkpoint" element={<AuthenticatedRoute element={<CheckpointList />} />}></Route>
<Route path="/vendour" element={<AuthenticatedRoute element={<VendourList />} />}></Route>
React Router Dom path are working normal but just that if I put AuthenticatedRoute to dashboard Route, it would not allow me in because the AuthenticatedRoute is not seeing the token I think but when I console the token in dashboard if i remove AuthenticatedRoute, the values are there.
Using React Router Dom, I was debugging this code and remove the AuthenticatedRoute middleware from dashboard and I was able to navigate to dashboard and get the localStorage value for the token and expiresIn but the point remains that when I put the AuthenticatedRoute middleware in the dashboad Route, the AuthenticatedRoute would bounce me back to login page meaning it cannot find the token.
Related
I have tried to add google auth login for my reactjs/expressjs web app. On the frontend I am using the react-google-login package:
import React from "react"
import { GoogleLogin } from "react-google-login"
import { useHistory } from "react-router-dom"
const axios = require('axios')
export default function LoginButton() {
let history = useHistory()
const { REACT_APP_GOOGLE_CLIENT_ID, REACT_APP_API_URL } = process.env
const onSuccess = async (response) => {
console.log(response)
const data = { token: response.tokenId }
const res = await axios.post(REACT_APP_API_URL + "/auth/google", data, {
'Content-Type' : 'text/json'
})
.then((res) => {
history.push('/home')
})
.catch((err) => {
console.log("[LOGIN FAILED]")
})
}
const onFailure = (response) => {
console.log(response)
}
return(
<GoogleLogin
clientId={REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Log in with Google"
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
/>
)
}
From what it seems I am getting the correct data from this button. I am sending the token to the expressjs api. This is where I am having issues. When using google-auth-library to verify the token I am getting the error: "Error: Token used too late, number_1 > number_2". As far as I know the idToken has expired, but isn't it weird considering I sent it as soon as possible from the frontend. Backend code:
const { OAuth2Client } = require('google-auth-library')
require('dotenv').config()
const client = new OAuth2Client(process.env.CLIENT_ID)
const postGoogleLogin = async (req, res) => {
const { token } = req.body
try{
const ticket = await client.verifyIdToken({
idToken: token,
audience: process.env.CLIENT_ID
})
const { name, email, picture } = ticket.getPayload()
res.status(201).json({
name: name,
email: email,
picture: picture
})
}
catch(e){
console.log(e)
}
}
I have tried to verify the token using the endpoint https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 which says the token is valid, but as far as I know this endpoint should not be used in production
The issue appears to be from the library itself. As long as you're using the same client id on both React and ExpressJs, verifyIdToken should return a success response as long as the token is still valid.
Also, you can make use of https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123 in your production code.
Internally, the library call the same endpoint to verify your token.
I am new to NuxtJS and have a problem, I am trying to get the token of the user who logged in from the website, the token is stored in the cookie, but, when I start or reload the website page (made with nuxt ssr (universal )), nuxtServerInit should and does start, but req.headers.cookie says it is undefined, so the data cannot be loaded into the store. If I reload the page, the cookie is still in the browser and everything is perfect, the problem is that the req.headers.cookie is: undefined, why? Ahhh, and in development it works perfectly, but in production it doesn't work (in nuxtServerInit the req.headers.cookie is not defined)
I am using Firebase Hosting, Cloud Features, Authentication, Cloud Firestore.
Here is the code:
// store/index.js
import { getUserFromCookie, getUserFromSession } from '../helpers/index.js'
export const actions = {
async nuxtServerInit({ app, dispatch }, { req, beforeNuxtRender }) {
console.log('req.headers.cookie: ' + req.headers.cookie)
console.log('req.session: ', req.session)
if (process.server) {
const user = getUserFromCookie(req)
console.log('process.server: ' + process.server)
console.log('user: ', user)
if (user) {
await dispatch('modules/user/setUSER', {
name: !!user.name ? user.name : '',
email: user.email,
avatar: !!user.picture ? user.picture : '',
uid: user.user_id
})
await dispatch('modules/user/saveUID', user.user_id)
} else {
await dispatch('modules/user/setUSER', null)
await dispatch('modules/user/saveUID', null)
}
}
}
}
// helpers/index.js
import jwtDecode from 'jwt-decode'
var cookieparser = require('cookieparser')
export function getUserFromCookie(req) {
if(req.headers.cookie){
const parsed = cookieparser.parse(req.headers.cookie)
const accessTokenCookie = parsed.access_token
if (!accessTokenCookie) return
const decodedToken = jwtDecode(accessTokenCookie)
if (!decodedToken) return
return decodedToken
}
return null
}
// pages/auth/signup.vue
<script>
import { mapActions } from 'vuex'
export default {
data () {
return {
email: '',
password: '',
renderSource: process.static ? 'static' : (process.server ? 'server' : 'client')
}
},
middleware: ['handle-login-route'],
methods: {
...mapActions('modules/user', [ 'login' ]),
async signUp () {
try {
const firebaseUser = await this.$firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
await this.writeUserData(firebaseUser.user.uid, firebaseUser.user.email)
await this.login(firebaseUser.user)
this.$router.push('/protected')
} catch (error) {
console.log(error.message)
}
},
writeUserData (userId, email) {
const db = this.$firebase.firestore()
return db.collection("Usuarios").doc(userId).set({
email: email
})
}
}
}
</script>
// store/modules/user.js
export const actions = {
async login({ dispatch, state }, user) {
console.log('[STORE ACTIONS] - login')
const token = await this.$firebase.auth().currentUser.getIdToken(true)
const userInfo = {
name: user.displayName,
email: user.email,
avatar: user.photoURL,
uid: user.uid
}
Cookies.set('access_token', token) // saving token in cookie for server rendering
await dispatch('setUSER', userInfo)
await dispatch('saveUID', userInfo.uid)
console.log('[STORE ACTIONS] - in login, response:', status)
},
async logout({ commit, dispatch }) {
console.log('[STORE ACTIONS] - logout')
await this.$firebase.auth().signOut()
Cookies.remove('access_token');
commit('setUSER', null)
commit('saveUID', null)
},
saveUID({ commit }, uid) {
console.log('[STORE ACTIONS] - saveUID')
commit('saveUID', uid)
},
setUSER({ commit }, user) {
commit('setUSER', user)
}
}
Thanks a lot! :D
if you are hosting it on Firebase you should rename your cookie to "__session", that's the only name you could use on Firebase. Their documentation should make it very clear!
Change the following part:
// helpers/index.js
const parsed = cookieparser.parse(req.headers.cookie)
const accessTokenCookie = parsed.__session
// store/modules/user.js
Cookies.set('__session', token)
I am using JWT to handle authentication and I have a update new password function. My problem is that how do I pass this JWT to my server in the UpdatePasswordAction ? Actually, I have that JWT in my cookies but when I submit the data to the server, the data is not passed correctly. Do I pass it right?
From my server, the JWT can not be retrieved.
console.log(JSON.stringify(req.cookies));// -> {}
console.log(req.headers.authorization);// -> undefined
In my User.actions.jsx, what I want to do is to get the token stored in cookies and pass the updatePasswordState which contains current password, new password, and new confirmed password.
import { Cookies } from 'js-cookie';
export const UpdatePasswordAction = (updatePasswordState) => {
return async (dispatch) => {
try {
// what I do is to pass the new password and JWT to the server to handle new password update
const token = Cookies.get('jwt');
const res = await axios.patch(`http://127.0.0.1:3000/users/updateMyPassword`, updatePasswordState, token);
const { data } = res;
dispatch({ type: UserActionTypes.UPDATE_PASSWORD_SUCCESS, payload: data });
alert('Update Password Successfully');
} catch (error) {
if (error.response) {
dispatch({
type: UserActionTypes.UPDATE_PASSWORD_FAIL,
payload: error.response.data.message,
});
console.log(error.response.data.message);
}
}
};
};
In my server, I have a middleware to check whether the user is login or not.
exports.protect = catchAsync(async (req, res, next) => {
//Getting token and check of it's there
let token;
console.log(JSON.stringify(req.cookies));// -> {}
console.log(req.headers.authorization);// -> undefined
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
token = req.headers.authorization.split(' ')[1];
} else if (req.cookies.jwt) {
token = req.cookies.jwt;
}
if (!token) {
return next(
new AppError('You are not logged in! Please log in to get access.', 401)
);
}
// rest jwt decode and verification
});
Any solution?
You are not correctly sending the data to the server. The axios#patch signature is this:
axios#patch(url[, data[, config]])
So you should change it to something like this:
await axios.patch(`http://127.0.0.1:3000/users/updateMyPassword`, updatePasswordState, {
headers: {
"Authorization": token
}
});
And on your server side you can access the Authorization header with req.headers.authorization
I am creating a website using the MERN stack however I don't know how to get data to the frontend that needs authorization from the backend and I tried to console log the problem and it shows me the HTML of my login page even though I am logged in. Any will be appreciated thank you so much.
My backend code:
router.get("/questions", ensureAuthenticated, (req, res) => {
math = Math.floor(Math.random() * 3) + 1;
Security.findOne({
user: req.user.id
}, (err, user) => {
if (err) {
console.log(err);
}
if (math === 1) {
res.send({
question: user.firstQuestion
});
} else if (math === 2) {
res.send({
question: user.secondQuestion
});
} else {
res.send({
question: user.thirdQuestion
});
}
});
});
My Frontend code:
class QuestionForm extends Component {
constructor(props) {
super(props);
this.state = {
data: ''
}
}
componentDidMount() {
axios.get("http://localhost:5000/users/questions")
.then((res) => {
this.setState({
data: res.data
});
}).catch((err) => console.log(err));
}
render() {
return <h1 > {
this.state.data
} < /h1>
}
}
a lot of changes should be made.
you never want to use the port in your Axios request
add to you package.json an proxy attribute
"proxy": "http://localhost:5000"
then you can change your axios get to
axios.get("/users/questions")
best practice when using autorization is to add to axios interceptors
follow this thread :
How can you use axios interceptors?
and also here is an example for using authorization with JWT token
const tokenHandler = axios.create();
tokenHandler.interceptors.request.use(config => {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = token;
}
return config;
});
export default tokenHandler;
let's say you create a token on the login page and store it inside your local storage.
now you can import the token handler and your request should look something like this :
import {tokenHandler} from '<TOKEN HANDLER PATH>'
..
..
tokenHandler.get("/users/questions")
.then((res)=>{
this.setState({data:res.data});
}).catch((err)=>console.log(err));
I am working on a web-app using node.js and vue.js, I am doing authentication and maintaining session using jwt and passport.js using passport-jwtstrategy
I have done all the things from creating jwt to protecting routes all the things now my issue is while generating jwt I am passing expiresIn:3600 so I want to auto-logout my user from Ui and remove token from localStorage once it has been one hour
On decoding my jwt I am getting
{
"name": "Dheeraj",
"iat": 1571896207,
"exp": 1571899807
}
So how can I get the real-time when to logout
In my auth.js vue store file my logout code when user clicks on logout is
logout({ commit }) {
return new Promise((resolve, reject) => {
localStorage.removeItem('jwt-token')
localStorage.removeItem('user-name')
commit('setAuthUser', null)
resolve(true)
})
},
In the same file, I have a method getAuthUser which is running whenever a page is loading or reloading to check to protect rout and guestUser
getAuthUser({ commit, getters }) {
const authUser = getters['authUser']
const token = localStorage.getItem('jwt-token')
const isTokenValid = checkTokenValidity(token)
if (authUser && isTokenValid) {
return Promise.resolve(authUser)
}
commit('setAuthUser', token)
commit('setAuthState', true)
debugger
return token
}
So how can I logout once my token is expired
Anyone out here please guide me how can I logout once the token is expired
Edit
In my router.js file
router.beforeEach((to, from, next) => {
store.dispatch('auth/getAuthUser')
.then((authUser) => {
const isAuthenticated = store.getters['auth/isAuthenticated']
if (to.meta.onlyAuthUser) {
if (isAuthenticated) {
next()
} else {
next({ name: 'login' })
}
} else if (to.meta.onlyGuestUser) {
if (isAuthenticated) {
next({ name: 'welcome' })
} else {
next()
}
} else {
next()
}
})
})
from my auth file I am calling get authUser which I have already mention above
for checking token validity I am using this code
function checkTokenValidity(token) {
if (token) {
const decodedToken = jwt.decode(token)
return decodedToken && (decodedToken.exp * 1000) > new Date().getTime()
}
return false
}
but it returns false when I am on login page and there is no token there but once I am loged in it shows null
My global api file
import axios from 'axios';
export default () => {
let headers = {
'cache-control': 'no-cache'
};
let accessToken = localStorage.getItem('jwt-token');
if (accessToken && accessToken !== '') {
headers.Authorization = accessToken;
};
return axios.create({
baseURL: 'http://localhost:8086/',
headers: headers
});
}
Refer to the axios documentataion: https://github.com/axios/axios
import axios from 'axios';
export default () => {
let headers = {
'cache-control': 'no-cache'
};
let accessToken = localStorage.getItem('jwt-token');
if (accessToken && accessToken !== '') {
headers.Authorization = accessToken;
};
const instance = axios.create({
baseURL: 'http://localhost:8086/',
headers: headers
});
instance.interceptors.response.use((response) => {
if(response.status === 401) {
//add your code
alert("You are not authorized");
}
return response;
}, (error) => {
if (error.response && error.response.data) {
//add your code
return Promise.reject(error.response.data);
}
return Promise.reject(error.message);
});
return instance;
}