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.
I am in the process of building a server-side app that is going to push the data into PowerBI's dataset. The only thing I'm struggling with is getting the bearer token to make a request to their API.
I'm getting the access token in the following way, however, I'm getting "401 Unauthorized" when using it in the request.
async getToken() {
const AuthenticationContext = adal.AuthenticationContext;
const context = new AuthenticationContext(`${ CONFIG.power_bi.authorityURI }/${ CONFIG.power_bi.tenantID }`);
return new Promise((resolve, reject) => {
context.acquireTokenWithClientCredentials(
CONFIG.power_bi.scope,
CONFIG.power_bi.clientID,
CONFIG.power_bi.clientSecret,
(error, tokenResponse) => {
if (error) {
console.log('PowerBI Token Acquire Error:', error);
return reject(tokenResponse == null ? error : tokenResponse);
}
console.log('PowerBI Token Acquired:', tokenResponse);
return resolve(tokenResponse);
}
);
});
}
async postRowsInDatasetTable() {
const token = await this.getToken().then(({ accessToken }) => accessToken);
const groupId = '{groupId}';
const datasetId = '{datasetId}';
const tableName = '{tableName}';
try {
const result = await axios.post(
`https://api.powerbi.com/v1.0/myorg/groups/${groupId}/datasets/${datasetId}/tables/${tableName}/rows`,
{
rows: [{
"status": "Deal",
"date": "2021-02-10T10:55:01.706Z"
}]
},
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-type': 'application/json'
}
}
);
} catch (error) {
console.log(error);
}
}
The thing is that data push functionality is working just fine with a token which I took from PowerBI's documentation page, where they provide example API calls, as it is populated automatically based on currently logged-in user (e.g. https://learn.microsoft.com/en-us/rest/api/power-bi/pushdatasets/datasets_postrowsingroup), which means I am simply getting the wrong token.
Could anyone please advise how to get the appropriate token, as I found nothing in the documentation?
Thanks
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 have a route on my backend application which should return an access token for a code sent from the frontend:
router.get('/token', (req, res) => {
const auth = googleService.getAuth();
auth.getToken(req.query.code, (error, res2) => {
const data = { code: 200 }
if (error) {
data.code = error.code;
data.error = error.response.data;
} else {
console.log(res2);
}
res
.status(data.code)
.send(data);
})
});
I retrive auth from googleService.getAuth():
const { google } = require('googleapis');
const keys = require('../config/keys');
var module = module.exports = {
getAuth: (token = false) => {
let auth = new google.auth.OAuth2(
keys.google.clientID,
keys.google.clientSecret,
keys.google.callbackURL
);
if (token) {
auth.credentials = {
access_token: token,
refresh_token: null
};
}
return auth;
},
youtube: google.youtube('v3')
};
In my config file, I have callbackURL:
module.exports = {
google: {
apiKey: 'XXXXXXXXXXXXXXXX',
clientID: 'XXXXXXXXXXXXXX',
clientSecret: 'XXXXXXXXXXXXXXX',
callbackURL: 'http://localhost:3000/google/redirect'
}
}
I also set it in my console:
However, I always have the following error when calling this route:
"error": {
"error": "redirect_uri_mismatch",
"error_description": "Bad Request"
}
The uri needs to match from the auth to the token. So it looks like you auth via your token endpoint and try to send the token to the google/redirect path. You can work around this.
To do so, verify that the redirect uri is whitelisted in your google project console. You can view this via the API Access (where you would see client ID, client secret, as well as a list of redirect uris)
It's possible that you can't have a redirect URI that is local.
If so, you can have an http valid address for you localhost in 2 mins with https://ngrok.com
Hope it'll help
I followed this document https://firebase.google.com/docs/auth/web/facebook-login
token is a new accessToken given from the client
const context = ({req}) => {
const credential = firebase.auth.FacebookAuthProvider.credential(token)
try {
firebase.auth().signInAndRetrieveDataWithCredential(credential)
} catch(error) {
console.error(error)
}
}
but I'm receiving this error (100% sure the token is copied correctly)
{ [Error: Your API key is invalid, please check you have copied it correctly.]
code: 'auth/invalid-api-key',
message:
'Your API key is invalid, please check you have copied it correctly.' }
{ _extensionStack:
GraphQLExtensionStack { extensions: [ [CacheControlExtension] ] } }
Am I doing this wrong?
Found a solution!
On the client side I had to change the facebook accessToken into an IdToken via:
getIdToken = async () => {
const provider = new firebase.auth.FacebookAuthProvider()
const result = await firebase.auth().signInWithPopup(provider)
const IdToken = await firebase.auth().currentUser.getIdToken(true)
}
Then verify the IdToken in the backend
try {
const decodedToken = await admin.auth().verifyIdToken(token)
console.log(decodedToken)
} catch (error) {
// error code
}