I'm currently building a microservice using Express. To retrieve an access token for a second microservice, I send a HTTP POST request to a token endpoint via axios. To reduce traffic and to simplify token management, a new token should only be requested when the current token is expired. If a new token is requested because the previous one expired, all callers should wait for this token instead of requesting several new tokens. To achieve this, I implemented the following class Auth that is instantiated as singleton:
class Auth {
getToken() {
if (this.token && !isExpired(this.token)) {
return Promise.resolve(this.token);
}
if (!this.tokenPromise) {
this.tokenPromise = axios
.post(...)
.then(response => {
console.log("received token");
this.tokenPromise = null;
this.token = response.data.token;
return response.data.token;
})
.catch(() => null);
}
return this.tokenPromise;
}
}
// export Auth class as singleton
module.exports = new Auth();
However, when I call getToken the returned promise never resolves and the statement console.log("received token"); is never called. For testing purposes I changed my code and returned another Promise instead of the axios promise:
class Auth {
getToken() {
if (this.token && !isExpired(this.token)) {
return Promise.resolve(this.token);
}
if (!this.tokenPromise) {
this.tokenPromise = axios
.post(...)
.then(response => {
console.log("received token");
this.tokenPromise = null;
this.token = response.data.token;
return response.data.token;
})
.catch(() => null);
this.secondPromise = Promise.resolve("fakeToken");
}
return this.secondPromise;
}
}
// export Auth class as singleton
module.exports = new Auth();
In this case both the returned promise and the axios promise resolve when calling getToken and console.log("received token"); is called. What is the reason for that and how could I achieve the desired behavior?
Minimum working example
During creation of this minimum working example, it turned out that the problem only occurs when calling the getToken()method from an axios interceptor (I followed these examples when creating the async interceptor). You can reproduce the issue with this minimum working example:
mwe.js:
const axios = require("axios");
class Auth {
getToken() {
if (!this.tokenPromise) {
console.log("get token");
this.tokenPromise = axios
.post("https://example.com")
.then((response) => {
console.log("received token");
this.tokenPromise = null;
return response;
})
.catch((err) => {
console.log("err", err);
this.tokenPromise = null;
return null;
});
}
return this.tokenPromise;
}
}
const auth = new Auth();
axios.interceptors.request.use(
async (config) => {
const token = await auth.getToken();
config.headers.Authorization = `Bearer ${token}`;
return config;
},
(error) => {
console.log("interceptor error", error);
}
);
axios.get("https://example.com");
package.json:
{
"name": "mwe",
"version": "1.0.0",
"description": "minimum working example",
"main": "mwe.js",
"scripts": {},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.19.2"
}
}
When running node mwe.js the message "get token" is logged, but not "received token".
Related
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,
},
});
});
having a real problem with getting this code to work. I have everything set up working great with Appwrite. I'm getting a response back from the server, but in my promise.then it finishes the other code and returns undefined from the login function. so then the post async function is sending a blank array in the try block. I've tried setting this code up every way I can think of but it never works. Sorry, i'm still trying to wrap my head around the promises and async js.
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
const { email, password } = await request.json();
function login() {
// add logic to authenticate user with external service here
const sdk = new Appwrite();
sdk
.setEndpoint('https://') // API Endpoint
.setProject('') // project ID
;
let promise = sdk.account.createSession(email, password);
let userLogin;
promise.then(function (response) {
console.log(response); // Success
userLogin = response.providerUid;
console.log(userLogin);
}, function (error) {
console.log(error); // Failure
});
console.log('login.json.js', { email, password: !!password });
console.log(userLogin);
return userLogin;
}
try {
const user = login();
locals.user = user;
return {
status: 200
};
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return {
status: 500,
body: message
};
}
}
You're returning userLogin in login before it's even populated in the asynchronous promise.then chain
Also, since you're currently handling the rejection in your promise.then(onFulfilled, onRejected) that would mean any rejection is handled inside login and your try/catch (once written correctly) would never have an error to catch since login handled it already
One more potential issue - if const { email, password } = await request.json(); rejects, then the error will be thrown to whatever called post - is that what you want? or did that also need to be handled inside post?
Anyway here's how to fix your code:
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
// note: if this throws then the error will be handled by whatever calls `post`
const { email, password } = await request.json();
function login() {
// add logic to authenticate user with external service here
const sdk = new Appwrite();
sdk
.setEndpoint('https://') // API Endpoint
.setProject('') // project ID
;
const promise = sdk.account.createSession(email, password);
return promise.then(function(response) {
let userLogin = response.providerUid;
return userLogin;
// or without redundant `userLogin` variable
// return response.providerUid;
});
}
try {
const user = await login();
locals.user = user;
return { status: 200 };
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return { status: 500, body: message };
}
}
Or, making login async
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
// note: if this throws then the error will be handled by whatever calls `post`
const { email, password } = await request.json();
async function login() {
// add logic to authenticate user with external service here
const sdk = new Appwrite();
sdk
.setEndpoint('https://') // API Endpoint
.setProject('') // project ID
;
let response = await sdk.account.createSession(email, password);
let userLogin = response.providerUid;
return userLogin;
}
try {
const user = await login();
locals.user = user;
return {
status: 200
};
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return {
status: 500,
body: message
};
}
}
Or, removing inner Login function completely
import { Appwrite } from 'appwrite';
export async function post({ locals, request }) {
// note: if this throws then the error will be handled by whatever calls `post`
const { email, password } = await request.json();
try {
const sdk = new Appwrite();
sdk.setEndpoint('https://') // API Endpoint
.setProject(''); // project ID
const response = await sdk.account.createSession(email, password);
console.log(response); // Success
locals.user = response.providerUid;
return { status: 200 };
} catch (error) {
const message = `Error in endpoint /api/login.json: ${error}`;
return { status: 500, body: message };
}
}
I am relatively new in React but I am trying to create a class/method for network call. Nothing complex just a way to make the code readable.
I have a class:
class Auth {
getToken(username, password) {
const endpointOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: `${username}`, password: `${password}` })
};
fetch(`${Constant.BASE_WP_URL}${Constant.TOKEN_ENDPOINT}`, endpointOptions)
.then(async response => {
const data = await response.json();
if (!response.ok) {
// get error message from body or default to response status
const error = (data && data.message) || response.status;
throw error;
}
return data;
})
.catch(error => {
throw error;
});
}
}
export default Auth;
I am trying to call it using :
import Auth from '../../data/network/Auth';
requestSignIn = (event) => {
event.preventDefault();
this.setState({loading: true})
try {
const authData = Auth.getToken(`${this.state.email}`, `${this.state.password}`);
sessionStorage.setItem('authToken', authData.token)
} catch (error) {
console.log("Connection to WP - Auth Token failed ")
console.error(error);
}
}
but React is complaining because getToken is not a function. I am trying to create a class Auth to have inside all methods/functions I need related to Auth process.
Also, is it the right way to handle the result ? is the try/catch as done works or should I do it differently as the getToken is an API call.
Any idea ?
pretty sure, it's easy but I can't find any interesting topics on Google.
Thanks
I think, if you want to use function directly in OOP of JavaScript, you must put static keyword in front of the function name.
In your auth file
static class Auth {
static getToken(username, password) {
...
}
}
In your index file
import Auth from '../../data/network/Auth';
const authData = Auth.getToken(`${this.state.email}`, `${this.state.password}`);
If you don't have static in front of the function name. You have to create a new instance of the class Auth in order to use the function inside.
import Auth from '../../data/network/Auth';
const AuthInit = Auth();
authData = AuthInit.getToken(`${this.state.email}`, `${this.state.password}`);
===========================
Update for applying asynchronous method
// ====== auth file
static class Auth {
static async getToken(username, password) {
...
// assign fetched data to data_fetch
const data_fetch = fetch(`${Constant.BASE_WP_URL}${Constant.TOKEN_ENDPOINT}`, endpointOptions)
.then(async response => {
const data = await response.json();
if (!response.ok) {
// get error message from body or default to response status
const error = (data && data.message) || response.status;
throw error;
}
return data;
})
.catch(error => {
throw error;
});
return data_fetch;
}
}
// ======= index file
import Auth from '../../data/network/Auth';
...
requestSignIn = async (event) => { // put async in front of your function
// the function outside (requestSignIn) must be async type
// in order to use await keyword for getToken() function
event.preventDefault();
this.setState({loading: true})
try {
// because your getToken function is now a async function, you can
// use "await" keyword in front of it to wait for fetching data to finish
const authData = await Auth.getToken(`${this.state.email}`, `${this.state.password}`);
sessionStorage.setItem('authToken', authData.token)
} catch (error) {
console.log("Connection to WP - Auth Token failed ")
console.error(error);
}
}
Hope this would help
but React is complaining because getToken is not a function
You've defined getToken as a method of an Auth instance, not a static function.
But you don't need an Auth class here at all, just use the proper exports/imports.
replace the Auth-class with:
export function getToken(username, password) {
//...
};
and you can either
/// import all exports from that file under the name `Auth`
import * as Auth from '../../data/network/Auth';
// ...
const authData = Auth.getToken(...);
or
// import these specific exports from that file.
import { getToken } from '../../data/network/Auth';
// ...
const authData = getToken(...);
The last option has the advantage that it can be tree-shaken. If You have some build-process, the compiler can eliminate all the pieces of code that you don't use; especially useful for libraries.
Edit:
Even if you want to keep the default import and import the entire thing, imo. it makes more sense to use a simple Object rather than a class with static methods.
function getToken(username, password) {
//...
}
export default {
getToken
};
In you class definition add static in front of your function to be
class Auth {
static async getToken(username, password) {
const endpointOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: `${username}`, password: `${password}` })
};
try {
const response = await fetch(`${Constant.BASE_WP_URL}${Constant.TOKEN_ENDPOINT}`, endpointOptions)
const data = await response.json();
if (!response.ok) {
const error = (data && data.message) || response.status;
throw error;
}
return data;
} catch (error) {
throw error
}
}
}
export default Auth;
then you will be able to call it as static function.
and requestSignIn will be using it in the following code
requestSignIn = async (event) => {
event.preventDefault();
this.setState({ loading: true })
try {
const authData = await Auth.getToken(`${this.state.email}`, `${this.state.password}`);
sessionStorage.setItem('authToken', authData.token)
} catch (error) {
console.log("Connection to WP - Auth Token failed ")
console.error(error);
}
}
having 2 api's. method POST-Login method GET-data. and server has cors enabled. Login api working fine, but when call api with GET method it gets failed.
Code:
->api Login-POST
const login = async (email, password) => {
console.log("in auth service");
const userDetail = {
username:email,
// email,
password
};
try {
// unsetHeadersWithUserToken();
const afterSuccess = await api.post(apiDetail.auth.url, userDetail);
if (afterSuccess) {
return afterSuccess.data;
}
} catch (error) {
console.log("error: ", error.response.error);
if (error.category === 'User Permissions') {
// forceLogout();
}
throw error;
}
};
->api-GET
try{
// console.log("url : ", apiDetail.partnerLocations.url);
let token = sessionStorage.getItem('token');
setHeadersWithUserToken(token);
let apiResponse = await api.get(apiDetail.partnerLocations.url);
return apiResponse;
}catch(error){
console.info('##### demand-response.js:11 #####');
console.info('========================= Start =========================');
console.error('error = ', JSON.stringify(error));
// console.log(error.response.data)
console.info('========================== End ==========================');
throw error;
}
->axios call
import axios from 'axios';
import { environment } from '../../utils/constants';
let api;
let apiDetail = {
baseURL: environment.baseURL,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
};
const setAPI = apiDetail => {
api = axios.create(apiDetail);
};
setAPI(apiDetail);
const setHeadersWithUserToken = token => {
api.defaults.headers.common['Authorization'] = token;
};
export {
api,
setHeadersWithUserToken,
};
Image-1
showing console error
Image-2
network call response
Try this
const proxyurl = "https://cors-anywhere.herokuapp.com/"
cosnt url = 'Your URL'
axios.get(proxyurl + url)
I faced the same issue and this works nicely.
Add the "proxy" property (found at the bottom here) to package.json:
"proxy": "http://localhost:<PORT-GOES-HERE>"
Now, instead of making HTTP requests like this:
axios.get("http://localhost:8080/example")
You should write them like this:
axios.get("/example")
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