I use to token authentication for my react spa app. Before I CRUD process, I check refresh token expiration time. If refresh token is valid, No problem but If is'nt valid it, I send old refresh token and receive new refresh and access token from server before crud.
No problem until this point.
Problem is the refresh mechanism completes without waiting for the response from the server.
currentUser store the token value.
I check null control for token and then if expiration time not valid, I send old refresh token.
At this point the function returns without waiting for the response of the authenticationService.createAccessTokenByRefreshToken function. Function must be wait this function because If access token is not valid, I need new one.
Why does the function end without waiting?
export function authHeader() {
var authorization = {
Authorization: ''
};
var currentUser = authenticationService.currentUserValue;
if (currentUser && currentUser.token) {
const refreshToken = currentUser.refreshToken;
const expiration = currentUser.expiration;
var moment = require('moment');
var now = moment();
if (moment(now).isAfter(expiration)) {
authenticationService.createAccessTokenByRefreshToken(refreshToken).then((res) => {
authorization.Authorization = `Bearer ${res.data.token}`;
return Promise.resolve(authorization);
});
}
else {
authorization.Authorization = `Bearer ${currentUser.token}`;
return Promise.resolve(authorization);
}
//return { Authorization: `Bearer ${currentUser.token}` };
} else {
return Promise.reject(authorization);
}
}
if (moment(now).isAfter(expiration)) {
authenticationService.createAccessTokenByRefreshToken(refreshToken).then((res) => {
authorization.Authorization = `Bearer ${res.data.token}`;
return Promise.resolve(authorization);
});
}
This block of code needs a return statement. At the moment it's implicitly returning undefined. Also, a small thing: if you're in a .then you don't need to do Promise.resolve(authorization), you can just return authorization. The .then creates a new promise for you.
if (moment(now).isAfter(expiration)) {
return authenticationService.createAccessTokenByRefreshToken(refreshToken)
.then((res) => {
authorization.Authorization = `Bearer ${res.data.token}`;
return authorization;
});
}
please start using async await. It makes your life easier and much readable code with fewer bugs. Let me know if the following works fine for you :)
const moment = require("moment");
const authHeader = async () => {
try {
const authorization = {
Authorization: ""
};
const currentUser = authenticationService.currentUserValue;
if (currentUser && currentUser.token) {
const refreshToken = currentUser.refreshToken;
const expiration = currentUser.expiration;
const now = moment();
if (moment(now).isAfter(expiration)) {
const authResponse = await authenticationService.createAccessTokenByRefreshToken(
refreshToken
);
authorization.Authorization = `Bearer ${authResponse.data.token}`;
} else {
authorization.Authorization = `Bearer ${currentUser.token}`;
}
}
return authorization;
} catch (err) {
return err;
}
};
export default authHeader;
Related
I'm trying to refresh jwt access token by using axios interceptor and refresh token.
The access token is refreshed and replaced properly in the local storage after a reissue request,
but reissue request does not stop. And when there is an error, it falls into a infinite loop which has already happened several times and caused shutdown...
I've tried to force the page to reload after getting the response thinking it is because access token is not saved properly in the local storage but i think it is not the case.
useAxios is a reissue custom hook that is used in every token required requests.
how do i stop this infinite loop?
const useAxios = () => {
const accessToken = localStorage.getItem("accessToken");
const refreshToken = getCookie("refreshToken");
const email = localStorage.getItem("email");
if(accessToken) {
axiosInstance.interceptors.request.use(async (req) =\> {
console.log(jwtDecode(accessToken).exp);
const expired = Date.now() >= jwtDecode(accessToken).exp * 1000;
if (!expired) return req;
try {
const res = await axios.get(
`${process.env.REACT_APP_BASE_URL}api/auth/re-issue/${email}`,
{
headers: {
Refresh: refreshToken,
},
},
{ withCredentials: true }
);
// console.log(res);
localStorage.setItem("accessToken", res.headers.get("Authorization"));
window.location.reload();
} catch (e) {
console.log(e);
}
return req;
});
}
return axiosInstance;
};
export default useAxios;
Im making a user authorization process with JWT tokens.
How does the flow look like?
User logs in - gets an access token and a refresh token from a server, as a response
Access token comes in json body and is saved in local storage. Refresh token comes in a httpOnly cookie.
User can use getAllUsers method untill access token is valid.
Whenever getAllUsers method returns 401 unauthorized (when access token expires), there is a request being sent to refresh token endpoint - getRefreshToken, which returns new access token that is being saved to local storage
Refresh token expires and user is being logged out.
Whole flow in Postman works but i have got problem at frontend side.
Function getAllUsers works until access token expires.
Thats why I made a global function in a util file that checks if a response is 401 and if so, it sends a request to get a new access token and calls a function which returned that error.
However it does not work.
I think that the problem is in getAllUsers function which immediately goes to catch block (when cant fetch list of users because of 401) and does not invoke that global function from util file. Console logs from both functions (getDataFromResponse, getRefreshToken) does not work so it does not even get there.
Any ideas??
API utils file
import { AxiosResponse } from "axios";
import { apiService } from "./api.service";
type ApiServiceMethods = keyof typeof apiService;
export const getDataFromResponse = async (
response: AxiosResponse,
funName: ApiServiceMethods,
...args: any
): Promise<any> => {
if (response.status === 401) {
console.log("error");
await apiService.getRefreshToken();
return await apiService[funName](args);
}
return response.data;
};
API Service:
import { getDataFromResponse } from "./api.utils";
import axios from "./axios";
type LoginArgs = {
password: string;
username: string;
};
const apiServiceDef = () => {
const login = async (args: LoginArgs) => {
try {
const response = await axios.post("/login", {
username: args.username,
password: args.password,
});
const { data } = response;
const { token } = data;
localStorage.setItem("accessToken", token);
return response;
} catch (e) {
throw new Error("Custom");
}
};
/* problem here */
const getAllUsers = async () => {
const Token = localStorage.getItem("accessToken");
try {
const response = await axios.get("/users", {
headers: {
Token,
},
});
return await getDataFromResponse(response, "getAllUsers");
} catch (e) {
console.log(e);
}
};
/* problem here */
const getRefreshToken = async () => {
try {
console.log("fetch new access token");
const response = await axios.get("/refreshToken");
if (response.status === 401) {
localStorage.removeItem("accessToken");
throw new Error("TokenExpiredError");
}
const { data } = response;
const { token } = data
localStorage.setItem("accessToken", token);
return response;
} catch (e) {
console.log(e);
}
};
return { login, getRefreshToken, getAllUsers };
};
export const apiService = apiServiceDef();
I usually use a wrapper around the async functions or just use axios interceptors (https://stackoverflow.com/a/47216863/11787903). Be sure that err.response.status is right property, not sure about that, but this solution should work for you.
const asyncWrapper = async (handler) => {
try {
return handler()
} catch (err) {
if (err.response.status === 401) {
// refresh token then again call handler
await refreshToken()
return handler()
}
}
}
const getAllUsers = asyncWrapper(() => {
const Token = localStorage.getItem("accessToken");
return axios.get("/users", {
headers: {
Token,
},
});
});
Good day I have a custom adonisjs command that pulls from an API.
async handle (args, options) {
// Status
// Open = 1979
// Get all jobs with open status.
const pullJobController = new PullJobsFromJobAdderController;
let token = await pullJobController.get_token();
if(token){
const jobs = await this._getOpenJobs('https://jobs/open-jobs', token , 1979);
}
}
async _getOpenJobs(url, accessToken, status) {
url = url + '?statusId=' + status
const headers = {
'Authorization': 'Bearer ' + accessToken
}
const options = {
method: 'GET',
url: url,
headers: headers
}
return (await rp(options).then(function (result) {
return {
status: true,
info: JSON.parse(result)
}
}).catch(function (error) {
return {
status: false
}
}));
} // _getOpenJobs()
PullJobsFromJobAdderController
async get_token()
{
// This works if directly returning the token.
// return "9ade34acxxa4265fxx4b5x6ss7fs61ez";
const settings = await this.settings();
const jobAdderObject = new this.JobAdder(settings.jobadder['client.id'], settings.jobadder['client.secret'])
const jobadderOauthObject = this.model('JobadderOauth');
const accessInfo = await jobadderOauthObject.jobdderLatestAccess();
let isAccessExpired = await this.checkAccessValidity(accessInfo.created_at);
let accessToken = accessInfo.access_token;
let apiEndpoint = accessInfo.api_endpoint;
if(isAccessExpired === true){
let refreshTokenInfo = await jobAdderObject.refrehToken(accessInfo.refresh_token)
if (refreshTokenInfo.status === true) {
let refreshTokenDetails = JSON.parse(refreshTokenInfo.info)
accessToken = refreshTokenDetails.access_token
apiEndpoint = refreshTokenDetails.api
await jobadderOauthObject.create({
code: accessInfo.code,
access_token: refreshTokenDetails.access_token,
refresh_token: refreshTokenDetails.refresh_token,
scope: 'read write offline_access',
api_endpoint: refreshTokenDetails.api
})
}
}
return accessToken;
} // get_token()
The function async get_token works as expected, it supplies me with a fresh token to be used by the adonisjs command. However it freezes after running the command.
But if I return the string token directly. The custom command handle() works as expected and terminates after running.
Scenario 1: (Directly returning the token string from PullJobsFromJobAdderController)
I run my custom command "adonis pull:jobs" and it runs as expected displaying in the terminal the result of the pulled data from the api.
Terminal is ready to accept another command.
Scenario 2: (Comment out the directly returned string token from PullJobsFromJobAdderController)
I run my custom command "adonis pull:jobs" and it runs as expected
displaying in the terminal the result of the pulled data from the
api.
Terminal is not accepting commands until I press ctrl+c and terminate the current job/command.
Perhaps I am missing something regarding async await calls.
Can someone point / help me to the right direction?
TIA
I got it, for anyone else having this kind of problem with adonis commands:
wrap the task inside your handle in a try... catch block then always have Database.close() and process.exit() in finally.
I'm trying to get axios-auth-refresh working, to ensure that my requests always have a valid auth token, but as far as I can tell the Axios request isn't continuing after axios-auth-refresh intercepts it.
I'm pretty new to JS development, so not sure if I've missed something obvious. I've looked through the documentation, but can't see any major differences in my implementation.
I'm running Node v13.2.0, v2.2 (latest) of axios-auth-refresh, and v0.18.1 of axios
My code is as follows:
require('axios-debug-log');
const axios = require('axios');
const axiosauthrefresh = require('axios-auth-refresh');
const instance = axios.create({
baseURL: 'https://api.example.com/api/v1.0',
});
let authToken = '';
const refreshAuthLogic = (failedRequest) => {
console.log('Intercepting auth');
instance
.post('/auth/login/', {
username: process.env.USER,
password: process.env.PASS,
skipAuthRefresh: true,
})
.then((tokenRefreshResponse) => {
authToken = tokenRefreshResponse.data.token;
failedRequest.response.config.headers.Authorization = `Token ${authToken}`;
console.log(`Auth token: ${authToken}`);
return Promise.resolve();
});
};
function getAuthToken() {
if (authToken) {
console.log(`Token exists: ${authToken}`);
return `Token ${authToken}`;
}
return null;
}
instance.interceptors.request.use((request) => {
console.log(`Requesting ${request.url}`);
const token = getAuthToken();
if (token) {
request.headers.Authorization = token;
}
return request;
});
axiosauthrefresh.default(instance, refreshAuthLogic);
module.exports = {
instance,
};
I make a request like this:
// nmcapi.js
const request= require('./request');
async function GetFolderInfo(volumeID, filerID, path) {
try {
const refreshResponse = await request.instance.get(`/volumes/${volumeID}/filers/${filerID}/path/${path}`);
console.log(`Refresh triggered: ${path}`);
} catch (error) {
console.log(error);
}
}
// interval.js
const nmcapi = require('./nmcapi.js');
const info = await GetFolderInfo('examplevolumeid', 'examplefilerid', 'examplepath')
And this is what I get as output:
Requesting /volumes/examplevolumeid/filers/examplefilerid/path/examplepath
axios GET /volumes/examplevolumeid/filers/examplefilerid/path/examplepath +1ms
axios Error: Request failed with status code 401 (GET https://api.example.com/api/v1.0/volumes/examplevolumeid/filers/examplefilerid/path/examplepath) +265ms
Intercepting auth
Requesting /auth/login/
TypeError: Cannot read property 'then' of undefined
at f (/home/sean/data-reports/node_modules/axios-auth-refresh/dist/index.min.js:1:1718)
at /home/sean/data-reports/node_modules/axios-auth-refresh/dist/index.min.js:1:2719
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at Object.GetFolderInfo (/home/sean/data-reports/server/nmcapi.js:29:29)
at /home/sean/data-reports/server/interval.js:25:18
at async Promise.all (index 0)
at Object.intervalFunc (/home/sean/data-reports/server/interval.js:36:18)
axios POST /auth/login/ +16ms
axios 200 OK (POST https://api.example.com/api/v1.0/auth/login/) +561ms
Auth token: 17412724ef5169eaab8502a9851480741e606ffa
As far as I can tell, the refreshAuthLogic function is working properly (because it returns a new auth token), but everything stops after that.
What am I missing?
I had missed an important point, the refreshAuthLogic function actually needs to return the axios instance instance.
The working implementation is:
const refreshAuthLogic = (failedRequest) => {
return instance
.post('/auth/login/', {
username: process.env.USER,
password: process.env.PASS,
skipAuthRefresh: true,
})
.then((tokenRefreshResponse) => {
failedRequest.response.config.headers.Authorization = `Token ${tokenRefreshResponse.data.token}`;
return Promise.resolve();
});
};
Thanks to Flyrell for answering this on GitHub
React newbie here, but proficient in Django.I have a simple fetch function which worked perfectly but then my project had no login authentication involved. Now that I have configured the login system, my backend refuses to serve requests with any access tokens. My login authentication is very new to me and was more or less copied from somewhere. I am trying to understand it but am not able to. I just need to know how to convert my simple fetch function to include the getAccessToken along the request in it's headers so my backend serves that request.
Here is my previously working simple fetch function :
class all_orders extends Component {
state = {
todos: []
};
async componentDidMount() {
try {
const res = await fetch('http://127.0.0.1:8000/api/allorders/'); // fetching the data from api, before the page loaded
const todos = await res.json();
console.log(todos);
this.setState({
todos
});
} catch (e) {
console.log(e);
}
}
My new login JWT authentication system works perfectly, but my previous code is not working and I keep getting error
"detail": "Authentication credentials were not provided."
This is is the accesstoken I am not able to 'combine' with my preivous fetch function:
const getAccessToken = () => {
return new Promise(async (resolve, reject) => {
const data = reactLocalStorage.getObject(API_TOKENS);
if (!data)
return resolve('No User found');
let access_token = '';
const expires = new Date(data.expires * 1000);
const currentTime = new Date();
if (expires > currentTime) {
access_token = data.tokens.access;
} else {
try {
const new_token = await loadOpenUrl(REFRESH_ACCESS_TOKEN, {
method: 'post',
data: {
refresh: data.tokens.refresh,
}
});
access_token = new_token.access;
const expires = new_token.expires;
reactLocalStorage.setObject(API_TOKENS, {
tokens: {
...data.tokens,
access: access_token
},
expires: expires
});
} catch (e) {
try {
if (e.data.code === "token_not_valid")
signINAgainNotification();
else
errorGettingUserInfoNotification();
} catch (e) {
// pass
}
return reject('Error refreshing token', e);
}
}
return resolve(access_token);
});
};
If you're looking for a way how to pass headers in fetch request, it's pretty straight forward:
await fetch('http://127.0.0.1:8000/api/allorders/', {
headers: {
// your headers there as pair key-value, matching what your API is expecting, for example:
'details': getAccessToken()
}
})
Just don't forget to import your getAccessToken const, if that's put it another file, and I believe that would be it. Some reading on Fetch method