Axios call to set authorisation header not working properly - javascript

I have a React Native app using Axios.
Im trying to authenticate the user and then set the authorization header from the response but im getting a weird order of execution.
The setClientToken is not being called before the code after it to do a get request is to be called. ie this is what i get logged:
Loggin in....
Authenticating...
Getting Recipes...
Auth... undefined
Done.
... [Error: Request failed with status code 401] //getRecipes call
Setting token
So you see setting token is done last and that means every axios after that will work but not the recipe.js call.
//app.js
async componentDidMount() {
console.log("Loggin in....");
await LogIn(getUser().username, getUser().password);
console.log("Done.");
}
render () {
//Recipe component rendered.
}
//recipesApi.js
export function getRecipes(category, offset, count) {
console.log("Getting Recipes...");
const url = `search?category=${category}&currentPage=${offset}&pageSize=${count}`;
console.log("Auth...", APIKit.defaults.headers.common["Authorization"]);
return APIKit.get(getUrl(url));
}
//user.js
import APIKit, { setClientToken } from "./apiKit";
export default async function LogIn(email, password) {
console.log("Authenticating...");
APIKit.post("/users/authenticate", {
username: email,
password: password,
})
.then((token) => setClientToken(token))
.catch((error) => console.log(error));
}
//APIKit.js
import axios from "axios";
// Create axios client, pre-configured with baseURL
let APIKit = axios.create({
baseURL: "http://192.168.1.4:4000/api",
timeout: 10000,
});
APIKit.interceptors.response.use(function (response) {
return response.data;
});
export const setClientToken = (token) => {
console.log("Setting token...", token.token);
APIKit.interceptors.request.use(
function (config) {
config.headers.Authorization = `Bearer ${token.token}`;
return config;
}, null, { synchronous: true }
);
};

Related

JWT auth process - frontend part

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

Unable to display API data on React frontend

I'm trying to return data fetched from a private API and display it on a page. My frontend use React JS and my backend use node with Express and Axion. My code work up to the point of returning the data. I get my APi Key and fetch my data but the data is not transferred to my page (Quotes.js).
Backend
app.js
import express from "express";
import { getCase } from "./getCase.js";
const app = express();
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
app.get("/", function (req, res) {
console.log("app.js call getCase");
res.send(getCase());
//console.log(req);
});
//console.log(Quote.getQuote());
let port = process.env.PORT;
if (port == null || port == "") {
port = 5000;
}
app.listen(port, function () {
console.log(`Server started on port ${port}...`);
});
Backend getCase
import { getToken } from "./nsApiToken.js";
import axios from "axios";
let getData = "";
console.log("begin of getCase");
const getCase = async () => {
let tokenRes = await getToken();
const url =
"https://5156735-sb1.app.netsuite.com/app/site/hosting/restlet.nl?script=860&deploy=1&recordtype=supportcase&id=717986";
try {
const res = await axios.get(url, {
headers: {
Authorization: `Bearer ${tokenRes.data.access_token}`,
},
});
return res;
} catch (error) {
return error;
}
};
export { getCase };
Frontend App.js
import logo from "./logo.svg";
import "./App.css";
import Quotes from "./Quotes.js";
function App() {
return (
<div className="App">
<header className="App-header">
<Quotes />
</header>
</div>
);
}
export default App;
Frontend Quotes.js
import React, { useState, useEffect } from "react";
import axios from "axios";
const Quotes = async () => {
const [text, setText] = useState([]);
const [author, setAuthor] = useState("");
const getQuote = await axios
.get("http://localhost:5000", {
crossdomain: true,
})
.then((res) => res.data)
.then((data) => {
setText({
data: data,
});
console.log("res: ", text);
});
return (
<div>
<button onClick={getQuote}>Generate Quote</button>
<h1>{text}</h1>
<h3>{author}</h3>
</div>
);
};
export default Quotes;
Process:
When I run my process the front execute and call Quotes.js in the axios get process.
app.js then route to home ('/') and call getCase via the app.get.
The getCase process execute get the API token and add it in the headers Authorization. The process initiate the call and fetch the data (if I console.log(res.data.fields.phone) or console.log(res.data.id) I see the correct data.
In my Quotes.js I want to display the data but res.data is empty, yet I get back status 200.
I've been trying to understand why it is not passing the data from the backend to the frontend.
There are several problems and some improvements to be made.
Backend
Problem - You are sending the entire AxiosResponse in the response from your Express app
Just send the data
const getCase = async () =>
(
await axios.get(
"https://5156735-sb1.app.netsuite.com/app/site/hosting/restlet.nl",
{
params: {
script: 860,
deploy: 1,
recordtype: "supportcase",
id: 717986,
},
headers: {
Authorization: `Bearer ${(await getToken()).data.access_token}`,
},
}
)
).data; // Return the data, not the whole response
Problem - getCase() is async
You need to await the result
app.get("/", async (req, res, next) => {
try {
res.json(await getCase());
} catch (err) {
next(err); // send the error to the Express error handler
}
});
Improvement - Creating your own CORS middleware is a waste of time
By the time you create a comprehensive CORS middleware, it will look exactly the same as the standard one so just use that
import express from "express";
import cors from "cors";
const app = express();
express.use(cors());
Frontend
Problem - React function components cannot be async
Function components must return a valid JSX node. Remove async from Quotes
Problem - getQuote should be a function
In order to trigger getQuote by button click, it needs to be a function
// if text is an object, initialise it as one
const [text, setText] = useState({});
const getQuotes = async () => {
try {
// there is no "crossdomain" Axios option
const { data } = await axios.get("http://localhost:5000");
setText({ data });
} catch (err) {
console.error(err.toJSON());
}
};
Problem - the text state is an object
JSX cannot render plain objects, you instead need to reference properties that can be rendered.
<h1>{text.data?.some?.property}</h1>
No idea what your response object looks like so this is just generic advice
The reason why this is not working is for two reasons. Firstly, res.data is not an asynchronous function. And since you are doing await, you can just get data. Secondly, you need to make your API calls and setState in the useEffect hook or else it would just end up in an infinite rerender situation. You just have to do the following and it should work:
useEffect(() => {
const fetchData = async () => {
const {data} = await axios
.get('http://localhost:5000', {
crossdomain: true
})
setText(data)
}
fetchData()
}, [])

Next-Auth signIn with Credentials is not working in NextJS

I'm integrating next-auth package to my fresh Next.js project. I have followed all of the Next.js and next-auth documentations but not able to find a solution.
The issue I'm facing goes like this:
I want to Login to my Next.js app using Email & Password submitted to my API Server running on Laravel.
When submitting the login form I'm executing the below function.
import { signIn } from "next-auth/client";
const loginHandler = async (event) => {
event.preventDefault();
const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("finished signIn call");
console.log(result);
};
And code shown below is in my pages/api/auth/[...nextauth].js
import axios from "axios";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
session: {
jwt: true,
},
providers: [
Providers.Credentials({
async authorize(credentials) {
axios
.post("MY_LOGIN_API", {
email: credentials.email,
password: credentials.password,
})
.then(function (response) {
console.log(response);
return true;
})
.catch(function (error) {
console.log(error);
throw new Error('I will handle this later!');
});
},
}),
],
});
But when try to login with correct/incorrect credentials, I get the below error in Google Chrome console log.
POST http://localhost:3000/api/auth/callback/credentials? 401 (Unauthorized)
{error: "CredentialsSignin", status: 401, ok: false, url: null}
Am I missing something here?
From the documentation (https://next-auth.js.org/providers/credentials#example)
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' }
if (user) {
// Any object returned will be saved in `user` property of the JWT
return user
} else {
// If you return null or false then the credentials will be rejected
return null
// You can also Reject this callback with an Error or with a URL:
// throw new Error('error message') // Redirect to error page
// throw '/path/to/redirect' // Redirect to a URL
}
}
You are not currently returning a user or null from the authorize callback.
Answer posted by shanewwarren is correct, but here is more elaborated answer,
Using axios to solve this
async authorize(credentials, req) {
return axios
.post(`${process.env.NEXT_PUBLIC_STRAPI_API}/auth/login`, {
identifier: credentials.identifier,
password: credentials.password,
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error.response);
throw new Error(error.response.data.message);
}) || null;
},

React static class/method call and Network call

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

Trying to get axios-auth-refresh working with NodeJS

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

Categories