I am building a Next.js application.
For authentication, I am using NextAuth.
For making HTTP calls, I am using Axios.
The problem I am facing is that I want to attach the JWT with every axios call I make.
I have an axios instance created as:
axios-client.js
const ApiClient = () => {
const defaultOptions = {
baseURL,
};
const instance = axios.create(defaultOptions);
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.log(`error`, error);
throw new Error(error.response.data.message);
},
);
return instance;
};
export default ApiClient();
I can get the jwt from getSession() function provided by next-auth.
But the problem is that function is asynchronous. If I try to get jwt from this from the getSession() function, I always get a "Promise" instead of value.
PS: I am using Strapi which sends the JWT after successful login.
Why can't you await the response from getSession() and add it as a header to your request. Note that you don't have to reinitialize axios client each time, you can reuse it.
Related
I'm working on a Nextjs app and I'm using Laravel api for auth and other things.
So I was searching about the best way to store the token that i will get when I sign a user in or sign him up from that Laravel external api and as I found storing it via httponly cookie is the best way and that's what I did using nextjs api routes to store it there.
I create 3 api routes in /api directory for loging the user in and up and out.
but now I'm facing an issue which is how to send this token on each request that i'm sending on client side to that api.
for now I'm using getServerSideProps to get the token and then send it by props to the needed component but this is solution is redundant and not handy at all.
I'm using getServerSideProps just to get it on any page that need to communicate with backend.
so is there any way to forward the token on each request without the need to get the token from server side and send it to client side?
or do u think there is a better way to store the token in somewhere else?
If you have a httpOnly cookie and you don't want to expose it to the client-side, I don't suggest to pass it in getServerSideProps as prop to the component. This will expose your token and will have the risk of scripts accessing the cookie. If the cookie is HttpOnly, it means it cannot be accessed through the client-side script.
Instead, you can create a NextJS api and access your token there. Then call that api in your app.
This is an example of how to access the cookie in nextjs api:
// src > pages > api > endpoint.js
export default async function (req, res) {
// assuming there is a token in your cookie
const token = req.headers.cookie.token;
// Attach the token to the headers of each request
const fetchWithToken = (url, options = {}) => {
options.headers = options.headers || {};
options.headers.Authorization = `Bearer ${token}`;
return fetch(url, options);
};
// Your API logic here
// ...
}
Now in your component in client-side, you can easily the new endpoint in /api/endpoint/ instead of calling the API with the token. In this method, you will never expose the token to the browser:
import { useState, useEffect } from 'react';
const MyComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/api/endpoint');
const data = await res.json();
setData(data);
};
fetchData();
}, []);
// Your component logic here
// ...
};
export default MyComponent;
I am using axios to make an api call to an api found on Apihub for a next JS app.
here is the code for the function to make the call to provide a list of property JSON objects.
export const baseUrl = "https://zillow56.p.rapidapi.com"
export const fetchApiListsingsCustom = async (url) => {
const { data } = await axios.get((url), {
method: 'GET',
headers: {
'X-RapidAPI-Key': '328713ab01msh862a3ad609011efp17e6b4jsn0e7112d5ee9a',
'X-RapidAPI-Host': 'zillow56.p.rapidapi.com'
}
});
data.then((res) => {
console.log(res);
})
.catch((error) => {
console.error(error);
});
return data.json();
}
When rendering the page I'm attempting to inject the response's data to dynamically create a list of apartment listings.
I'm trying to use getServerSideProps so that the data is already available by the time a user requests the page. After fetching the data, I want to also print them in the terminal to validate it's success.
export default function Home({ propertiesCustomdata })
export async function getServerSideProps() {
const propertiesCustom = await fetchApiListsingsCustom(`${baseUrl}`)
const propertiesCustomdata = propertiesCustom.json()
return {
props: {
propertiesCustomdata
}
}
}
The problem is, I seem to be getting a 404 error from the axios call, before the page gets a chance to load. When I access this I get a 404 error but I also manage to receive some contents of the call the API was to make.
My apologies if this is unclear, but this is all I know to report on this so far.
Studying async and await, fetch, and axios. Very confusing.
I'm building a webapp using react and flask. I'm currently implementing user login features using react-token-auth and flask_praetorian. I've already created my back-end functions that handle logging in and can successfully return a token. However, I am now having issues with making an authenticated request.
My flask function
#app_login.route('/get_username')
#flask_praetorian.auth_required
def protected():
response = jsonify({'username': flask_praetorian.current_user().username})
return response
and on react
const fetchUsername = () => { authFetch(`http://${configData.LOCAL_SERVER}:${configData.WEBAPP_PORT}/get_username`).then(response => {
return response.json()
}).then(response => {
console.log(response)
})
}
I'm using the default createAuthProvider as shown on the react-token-auth project page
export const { useAuth, authFetch, login, logout } = createAuthProvider({
getAccessToken: session => session.accessToken,
storage: localStorage,
onUpdateToken: token =>
fetch(`http://${configData.LOCAL_SERVER}:${configData.WEBAPP_PORT}/app_login/refresh`, {
method: 'POST',
body: token.refreshToken,
}).then(r => r.json()),
});
Whenever I make a request, I get a 401 (UNAUTHORIZED) and react returns the error 'authFetch' was called without access token. Probably storage has no session or session were expired
I've checked my local storage and I can see that the key is there:
Storage {persist:root: '{"player":"{\\"value\\":{\\"name\\":\\"\\",\\"access_toke…_persist":"{\\"version\\":-1,\\"rehydrated\\":true}"}', REACT_TOKEN_AUTH_KEY: '"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2…5OX0.rTBCD7YPD8wrB95v1j9oazNLusKOPErI5jed_XWXDhU"', length: 2}
I'm really trying to avoid using technologies like Redux so any assistance regarding this specific setup would be great.
I'm using React with axios mainly. There I have an interceptor for API calls to refresh my JWT token when it expires.
<img src="/media/cache/img.jpg" alt={row.id} width={45} height={45}>
These are also loaded from the server and authentication is needed. But when the token expires and no API query is needed, these images won't load because the token is invalid and authentication is required for these images.
Can I somehow achieve that even in these scenarios the tokens are refreshed correctly before loading the image?
You can use axios to fetch images as well. It looks something like this:
const url = "/media/cache/img.jpg";
const [objectURL, setObjectURL] = useState("");
useEffect(() => {
axios
.get(url, {
responseType: "blob",
})
.then((res) => {
const new_blob = new Blob([res.data], { type: "image/jpg" });
setObjectURL(URL.createObjectURL(new_blob));
});
}, []);
<img src={objectURL} alt={row.id} width={45} height={45}>
Now you can modify this to use your "interceptor for API calls" to refresh your token.
quick question on implementation of multiple axios instances.
I call my own API as needed inside my mobile app. Certain API endpoints are protected by checking that the token issued by Google Signin or Facebook Signin is authenticated and matches the expected user. Other endpoints do not require a token and actually require the frontend to not send a token at all to proceed down the proper logic path on the backend.
I set the global axios instance when I fetch the user token on frontend. I am hoping that all axios calls include the token by default. To achieve this, I set the default Authorization header as such:
import axios from 'axios'
...
axios.defaults.headers.common.Authorization = `Bearer ${fbUserInfo.accessToken}`
This code runs on the initial page of the app that is loaded, so everytime I use axios somewhere else in the app, the token is now included. It works as expected until I proceed to the following step.
In order to create some recurring API calls where a token should not be included, I create a "tokenless" instance inside one of my redux action creators.
const axiosTokenlessInstance = axios.create()
...
axiosTokenlessInstance.defaults.headers.common.Authorization = false
This does indeed override the default axios settings when I call it directly, however, I've realized that it also has overridden my default settings when I just call axios directly again.
I expected the behavior to be that I could still call axios.post or axios.get elsewhere and it would still include the token. Was I mistaken and do I have to create a "tokened" instance? I'd prefer not to do this as I'd have to go through and replaced tokened instance explicitly everywhere rather than just using axios default. Thanks!
Try creating a file called setAuthToken.js that handles the auth instance separately. This way any time you need to use the auth, you can just call this function ( I would even suggest limiting to calling this auth only once and having the token saved in the users localStorage):
import axios from 'axios';
const setAuthToken = token => {
if(token){
axios.defaults.headers.common['x-auth-token'] = token;
}
else{
delete axios.defaults.headers.common['x-auth-token'];
}
}
export default setAuthToken;
Now let's say you have a backend function call that needs auth verification, you can do it like so:
import setAuthToken from "../utils/setAuthToken";
export const loginUser = (token) => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOGGED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
Specifying the blank header Authorization inside axios.create() fixes the issue and doesn't override the global axios headers.
So this works as expected:
const axiosTokenlessInstance = axios.create({
baseURL: Config.API_HOST,
headers: { Authorization: '' },
})
While this overrides global axios settings, not just affecting the specified instance:
const axiosTokenlessInstance = axios.create()
axiosTokenlessInstance.defaults.headers.common.Authorization = false