How to logout once jwt token is expired - node.js

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;
}

Related

Implementation of rxjs BehaviourSubject in Next.js for state management not working

Trying to store jwt token on login using rxjs behavioursubject
Then creating a http request with Authorization: Bearer ${user.jwtToken} in the
I believe I need to have
a) initial value,
b) a source that can be turned into an observable
c) a public variable that can be subscribed
On log in the user is correctly added to the user subject here "userSubject.next(user);"
But whenever I try to create the bearer token its always null
// The Accounts Service
// initialise and set initial value
const userSubject = new BehaviorSubject(null);
const authApiUrl = "https:testApi";
export const accountService = {
` user: userSubject.asObservable(), get userValue() { return userSubject.value },
login,
getAllUsers
};
function login(email, password) {
return fetchWrapper.post(process.env.AuthApiUrl + '/accounts/authenticate', { email, password })
.then(user => {
userSubject.next(user);
localStorage.setItem('user', JSON.stringify(user));
return user;
});
}
function getAllUsers() {
return await fetchWrapper.get(process.env.AuthApiUrl + '/accounts/get-all-users');
}
}
// The fetchwrapper
export const fetchWrapper = {
get,
post
};
function post(url, body) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeader(url) },
credentials: 'include',
body: JSON.stringify(body)
};
return fetch(url, requestOptions).then(handleResponse);
}
function get(url) {
const requestOptions = {
method: 'GET',
headers: authHeader(url)
};
return fetch(url, requestOptions).then(handleResponse);
}
function authHeader(url) {
// return auth header with basic auth credentials if user is logged in and request is to the api url
// THE accountService.userValue IS ALWAYS NULL???
const user = accountService.userValue;
const isLoggedIn = user && user.jwtToken;
const isApiUrl = url.startsWith(process.env.AuthApiUrl);
if (isLoggedIn && isApiUrl) {
return { Authorization: `Bearer ${user.jwtToken}` };
} else {
return {};
}
}
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
if ([401, 403].includes(response.status) && accountService.userValue) {
// auto logout if 401 Unauthorized or 403 Forbidden response returned from api
accountService.logout();
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}

NodeJS ReactJS Redux displays undefined for token api token

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.

how to pass jwt token into the header

I make web application using react js, node, express
when I login the error message appear says "No token attached"
now I need to put a jwt token into header how can I do that
this is my code:
import { webToken } from "../crypto/web_token.js";
import { responses } from "../classes/responses.js";
export const verifyRequest = (req, res, nex) => {
try {
if (!req.headers.authorization) {
throw Error("no token attached");
}
const token = req.headers.authorization.split(" ")[1];
const payload = webToken.verify(token);
req.user = payload;
nex();
} catch (error) {
res.json(new responses.Error(error.message));
}
};
another code: web_token.js
import jsonwebtoken from "jsonwebtoken";
import { errors } from "../classes/errors.js";
const secret = "#########";
export const webToken = Object.freeze({
generate: (data, expiry = "1hr") => {
try {
return jsonwebtoken.sign(data, secret, { expiresIn: expiry });
} catch (error) {
throw new errors.Logic("Internal error from the bcrypt hashing", "jwt");
}
},
verify: (token) => {
try {
const data = jsonwebtoken.verify(token, secret);
return data;
} catch (error) {
throw new errors.Authentication(
error.message.replace("jwt", "Token"),
"jwt"
);
}
},
});
here is the template, take a look
var axios = require('axios');
var data = JSON.stringify({
"value1": "val1"
});
var config = {
method: 'post',
url: 'http://localhost:3000/GetText',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
data : data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});

Memory Leak with axios-module from Nuxt

I have a memory leak using #nuxtjs/axios. I don't really know if it's my code or the plugin.
My project requires 2 axios instances to run.
One to get an "anonymous token" to be allowed to access the API.
And a second one to use that token and make my API calls.
const createDefaultClient = (agent, $axios, $appInsights, inject) => {
const defaultClient = $axios.create({
withCredentials: true,
httpsAgent: agent
})
defaultClient.setBaseURL(
process.server ? 'http://localhost:3000' : window.location.origin
)
defaultClient.onResponseError(error => {
logError(error, $appInsights)
return Promise.reject(error)
})
inject('defaultClient', defaultClient)
return defaultClient
}
const createApiClient = (
agent,
$axios,
$cookies,
$appInsights,
defaultClient,
inject,
request
) => {
const apiClient = $axios.create({
withCredentials: true,
httpsAgent: agent
})
apiClient.setBaseURL(process.env.NUXT_ENV_BASE_API)
apiClient.onRequest(config => {
const cookieToken = $cookies.get(accessTokenCookieName)
if (cookieToken) {
config.headers.common.Authorization = `Bearer ${cookieToken}`
}
debug(`${config.__isRetryRequest ? 'Retry' : 'Request'}: ${config.url}`)
})
apiClient.onResponseError(async error => {
const originalRequest = error.config
debug(`Error ${get(error, 'response.status')}: ${error.config.url}`)
// if we get error 401 (token expiration) we will refresh the token and retry to access API
if (error.config && error.response && error.response.status === 401) {
originalRequest.__isRetryRequest = true
const refreshToken = $cookies.get(refreshTokenCookieName)
let fetchAnonymousToken = true
let response = null
// if we had a cookie try to refresh it
if (refreshToken) {
response = await defaultClient.$post(`/forms/refresh`, {
refreshToken
})
if (!response) throw new Error('Auth failure')
if (process.client) {
createAuthCookies($cookies, response, request)
}
fetchAnonymousToken = false
}
// else fetch an anonymous cookie
if (fetchAnonymousToken) {
response = await defaultClient.$get(`/forms/anonymous`)
if (!response) throw new Error('Auth failure')
if (process.client) {
createAuthCookies($cookies, response, request)
}
}
// resend API request with the new valid token
originalRequest.headers.Authorization = `Bearer ${get(
response,
'access_token'
)}`
return apiClient(originalRequest)
} else {
logError(error, $appInsights)
return Promise.reject(error)
}
})
inject('apiClient', apiClient)
}
export default function({ $axios, app, req }, inject) {
const { $cookies} = app
const agent = new https.Agent({
rejectUnauthorized: process.env.NODE_ENV !== 'development'
})
const defaultClient = createDefaultClient(agent, $axios, inject)
createApiClient(
agent,
$axios,
$cookies,
defaultClient,
inject,
req
)
}
Basically I've followed the doc here : https://axios.nuxtjs.org/extend
I've commented everything in my projet and without the API calls the memory get garbage collected.
Here the memory before the charge
Here after the charge
After a siege, we can clearly see that the memory won't go down :/
Please help me i'm desesperate..

How do I store JWT Token after receiving from Cognito? Logging in via the Cognito Hosted UI

Architecture: front end Angular, backend nodejs/express.
Currently the setup works as follow:
Login to the site via the Cognito Hosted UI
This redirects to our home page and sends us a code in the URL
I pull down this code in Angular
import { Component, OnInit } from '#angular/core';
import { DbService } from '../db.service';
import { Iss } from '../db.service';
import { Router, ActivatedRoute } from '#angular/router';
import { Http, Response, RequestOptions, Headers} from '#angular/http';
#Component({
selector: 'app-dashboard'
})
export class GroupSelectionComponent implements OnInit {
cognitoCode: string;
constructor(
private DbService: DbService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.route.queryParams
.subscribe(params => {
console.log(params);
console.log(params.code);
this.cognitoCode = params.code;
});
this.DbService.getIss(this.cognitoCode).subscribe(
iss => this.iss = iss
);
}
In the code you will see I am passing the congitocode to the dbservice for getIss.
db.service
getIss(cognitoCode ): Observable<Issuer[]> {
const url = hosturl +'i_l';
// let header: HttpHeaders = new HttpHeaders();
const httpOptions = {
headers: new HttpHeaders({
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
'Authorization': cognitoCode
})
};
let params = new HttpParams()
console.log(httpOptions.headers);
return this._http.get(url, httpOptions)
.pipe(
map((res) => {
console.log(res);
return <Issuer[]> res;
})
);
}
I then send the code as part of the headers of my GET request to the backend.
The GET then hits my backend router with these settings.
var authMiddleware = require('../middleware/AuthMiddleware.js');
router.get('/i_l', authMiddleware.Validate, i_l.get);
This will then call my authMiddleware which takes the code provided by Cognito Hosted UI and use a POST against oauth2/token to get my JWT token.
That token is then parsed used to compare to the https://cognito-idp.us-east-2.amazonaws.com/REMOVED/.well-known/jwks.json for congnito.
Once validated the request continues and I get data back from the backend.
// POST that trades the code for a token with cognito
var options = {
'method': 'POST',
'url': 'https://REMOVED.amazoncognito.com/oauth2/token',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'grant_type': 'authorization_code',
'client_id': 'CLIENTIDREMOVED',
'code': req.headers['authorization'],
'redirect_uri': 'http://localhost/group-selection'
}
};
// First request gets the JSON request of the token using the POST above
request(options, function (error, response) {
if (error) throw new Error(error);
token = JSON.parse(response.body).access_token;
//localStorage.setItem('token', token);
// request pull down status based on validitiy of token
request({
url : `https://cognito-idp.us-east-2.amazonaws.com/REMOVED/.well-known/jwks.json`,
json : true
}, function(error, response, body){
console.log('token: ' + token);
if (!error && response.statusCode === 200) {
pems = {};
var keys = body['keys'];
for(var i = 0; i < keys.length; i++) {
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
pems[key_id] = pem;
}
var decodedJwt = jwt.decode(token, {complete: true});
if (!decodedJwt) {
console.log("Not a valid JWT token");
res.status(401);
return res.send("Not a valid JWT token");
}
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log('Invalid token - decodedJwt.header.kid');
res.status(401);
return res.send("Invalid token - decodedJwt.header.kid");
}
jwt.verify(token, pem, function(err, payload) {
if(err) {
console.log("Invalid Token - verify");
res.status(401);
return res.send("Invalid token - verify");
} else {
console.log("Valid Token.");
return next();
}
});
} else {
console.log("Error! Unable to download JWKs");
res.status(500);
return res.send("Error! Unable to download JWKs");
}
});
});
Quesiton -- how I set this up so that the Token I get back continues for the user?
If I understand your question properly then you are trying to validate all your apis through cognito user right?
Then you just need to do two things.
Add in header JWT token once you are getting after login. Just store into your application scope and pass everytime whenever any API is calling.
Auth.signIn(data.username, data.password)
.then(user => {
let jwkToken = user.getSignInUserSession().getAccessToken().getJwtToken();
// Store above value in singletone object or application scope.
})
.catch(err => {
//error
});
Now When API is calling pass jwkToken as header.
Then Go AWS ApiGateWay Console and add into Authorizers.

Resources