I am trying to create and axios instance so that I don't have to write Authorization header on every request. I am storing access token on localStorage.
I tried creating a file axiosInstance.ts
import axios, { AxiosInstance } from "axios"
const service = axios.create({
baseURL: "http://localhost:5000/",
headers: {
Authorization: localStorage && localStorage.getItem("token")
? `Bearer ${localStorage.getItem("token")}`
: "",
},
})
export default service
But it throws localStorage is not defined error.
So I tried to make this a custom hook like this
useAxios.ts
function useAxios() {
const [axiosInstance, setAxiosInstance] = useState<null | AxiosInstance>(null)
useEffect(() => {
const service = axios.create({
baseURL: "http://localhost:5000/",
headers: {
Authorization: localStorage.getItem("token")
? `Bearer ${localStorage.getItem("token")}`
: "",
},
})
setAxiosInstance(service)
}, [])
return axiosInstance
}
export default useAxios
And when i tried to use it like this it threw an error which says service.post is not a function
So when i log the service/axiosInstance it logs a get request promise to baseURL. I am not sure what i am doing wrong.
Related
When I try to login to my react app it returns as success and saves it in sessionStorage but when I try to access the page which requires Bearer token to fetch data via axios it returns http 401 error.
But when I reload the page, then the result is as required.
import axios from "axios";
let token = JSON.parse(window.sessionStorage.getItem("token"));
let AxiosInstance= axios.create({
baseURL: "https://myurl.com/backend/api/",
timeout: 5000,
headers: { Authorization: "Bearer " + token },
});
export default AxiosInstance;
Your token is being initialized onload so you have to call token in config.headers["Authorization"] on every request through interceptor so you don't need to reload page.
Interceptor:
Axios interceptors are functions that are called before a request is sent and after a response is received
import axios from "axios";
let AxiosInstance = axios.create({
baseURL: "https://myurl.com/backend/api/",
timeout: 5000,
});
AxiosInstance.interceptors.request.use(function (config) {
let token = JSON.parse(window.sessionStorage.getItem("sessionData"));
config.headers["Authorization"] = "Bearer " + token;
return config;
});
export default AxiosInstance;
UPDATE: You can learn more here about Axios interceptors
I'll guess it is because you're creating the axios instance outside any of React's lifecycle
when you run your app and enter your login screen, it makes a Axios instance
import axios from "axios";
let token = JSON.parse(window.sessionStorage.getItem("token"));
let AxiosInstance = axios.create({
baseURL: "https://myurl.com/backend/api/",
timeout: 5000,
headers: { Authorization: "Bearer " + token }, // on the first go token is null
});
export default AxiosInstance;
After you login, your axios instance still has a nulll token, you see, this code is executed once and never more, that's why when you refresh the page, it works, because when this piece of code is executed once again, there's a token in localstorage.
There's a couple things you can do but the easiest i guess it's to use Axios interceptors
AxiosInstace.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
That way every time you do a request your headers will be updated (not sure if this is the best approach but it works)
I want to add an authorization to header, I've already assign to header before like in the code below
const axiosIns = axios.create({
baseURL: 'http:$$$$$$$$$$/api/app',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
Then I added interceptor and assigned token to authorization header
axiosIns.interceptors.request.use(async config => {
const accessToken = await authService.getAccessToken()
config.headers.Authorization = `Bearer ${accessToken}`
return config
})
but typescript put red line below config.headers says Object is possibly 'undefined'
The error message "Object is possibly 'undefined'" is indicating that the TypeScript compiler is not sure if the "config.headers" object is defined or not. This can happen if the "config" object passed to the request interceptor does not have a "headers" property. To fix this, you can add a check for the "headers" property before trying to access it.
You can add a check to ensure the headers object is defined before trying to add the Authorization header.
axiosIns.interceptors.request.use(async config => {
const accessToken = await authService.getAccessToken()
if (config.headers) {
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
});
This way, if the headers property is undefined, you will skip the assignment and not get the error.
axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
config.headers['Authorization'] = auth;
return config
})
you can red more there
I send a request to the API for creating an account(I use axios) then API send me a response involve a token. I save this token in local storage.But I don't know how to send it in axios header.
if (this.sendRequest) {
axios.post(url, data)
.then((res) => {
if (res.data.type === "success") {
localStorage.setItem("token",res.data.data);
}
})
.catch((err) => this.msg.push("error" + err.response.status));
}
To do this, follow the steps below:
1- create a folder in your project called Services. Then create another directory in that folder called Config. Inside that folder create a .js file called auth-axios.js as follows:
In this file, you use the following code. By doing this, you inform the application that every time you want to call the API, you must go through this port and you must set the base URL and header in the API automatically:
import axios from "axios";
import authService from "../modules/authService";
import Vue from "vue";
const headers = {
"content-type": "application/json",
Accept: "application/json",
"Accept-Language": "fa",
version: "1000"
};
const API_V1 = axios.create({
baseURL: process.env.VUE_APP_BASE_URL_V1,
headers: headers
});
const API_DEV = axios.create({
baseURL: process.env.VUE_APP_BASE_URL_DEV,
headers: headers
});
API_DEV.interceptors.request.use(
config => {
const token = authService.getAccessToken();
if (token) {
config.headers["Authorization"] = "Bearer " + token;
}
return config;
},
error => {
Promise.reject(error);
}
);
export { API_V1, API_V4, API_DEV };
Now for the services that you have in the application, you have to create a separate file in the same services directory and use the API_1 variable in that file.
for example, create accountServices.js and on this file call API by this way:
import { API_V1 } from "../config/auth-axios";
class employerServices {
createAccount(body) {
return API_V1.post("test-API-URL", body);
}
}
export default new employerServices();
I have a react app.
I have an axios component I want to reuse:
import axios from 'axios'
import dynamic from 'next/dynamic'
const baseUrl = 'http://127.0.0.1:8000/'
const axiosInstance = axios.create({
baseURL: baseUrl,
timeout: 5000,
headers: {
Authorization: localStorage.getItem('access_token')
? 'Bearer ' + localStorage.getItem('access_token')
: null,
'Content-Type': 'application/json',
accept: 'application/json',
}
})
export default axiosInstance
Now, I try and import this into my registration page as follows:
import axiosInstance from "axiosInstance"
The file itself looks like this:
const handleFormSubmit = async (values: any) => {
axiosInstance.post(`account/register/`, {
username: values.username,
email: values.email,
password: values.password,
confirm_password: values.confirm_password,
}).then((response) => {
console.log(response);
});
// router.push('/profile')
console.log(values);
};
However, this throws an error:
Can some please help me with this issue? I am new to Nextjs and looked at
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
but not sure how to use it in this context.
localStorage is a propert on window object, and since next.js is a server side rendering framework, when next renders the component on server, window.localStorage will be undefined.
In order to import it, set the axios instance like this:
const axiosInstance = axios.create({
baseURL: baseUrl,
timeout: 5000,
headers: {
// if localStorage is not defined, it wont throw error
Authorization:localStorage && localStorage.getItem('access_token')
? 'Bearer ' + localStorage.getItem('access_token')
: null,
'Content-Type': 'application/json',
accept: 'application/json',
}
})
and then inside
localStorage is part of the browser's storage and not the server's. You could use an environment variable and use process.env.ACCESS_TOKEN
More can be read here
Basically you create a file called .env.local
There is built in support in NextJS for this file.
Here you defined your variables as follows
ACCESS_TOKEN=myAccessTokenIsAwesome
SOMETHING_ELSE=myOtherKeyINeed
In your code you can then use the process.env.ACCESS_TOKEN to use this defined environment variable
I'm pretty much copy/pasting the example code from the Apollo GraphQL Network Layer Docs on Creating Middleware, except I'm pulling my dependencies from apollo-client-preset and I'm using a simple utility library to return the jwt token. Here's the link code...
import { ApolloClient, HttpLink, InMemoryCache, ApolloLink, concat } from 'apollo-client-preset';
import { getSessionToken } from './api/localStorage';
const httpLink = new HttpLink({ uri: 'http://localhost:4000/' });
const authMiddleware = new ApolloLink((operation, forward) => {
const token = getSessionToken();
const authorizationHeader = token ? `Bearer ${token}` : null;
operation.setContext({
headers: {
authorization: authorizationHeader,
}
});
return forward(operation);
})
const client = new ApolloClient({
link: concat(authMiddleware, httpLink),
});
And here's the ./api/localStorage method in play...
export const getSessionToken = () => {
return AsyncStorage.getItem(AUTH_TOKEN);
}
Executing the above code results in an HTTP request with the following headers...
POST / HTTP/1.1
Host: localhost:4000
Content-Type: application/json
User-Agent: Expo/2.2.0.1011489 CFNetwork/893.14 Darwin/17.3.0
Connection: keep-alive
Accept: */*
Accept-Language: en-us
Authorization: Bearer [object Object]
Accept-Encoding: gzip, deflate
Content-Length: 174
Notice the Authorization: Bearer [object Object] bit
I think the problem is pretty obvious. AsyncStorage is an async function, and I need to wait for the promise to be resolved before I assemble the authorization string. The solution, however, is less obvious. I've tried various methods, but no love so far.
I've seen a couple examples similar to the above. Here's another in the How to GraphQL Authentication Module. Neither shows how to wait for the asyncstorage promise to resolve first. I know Apollo waits for the promises for you automagically sometimes, so I thought that might be the case here. But doesn't seem to be in my experience.
So what's the solution?
Here's a couple things I've tried, but that have failed.
1. Promise.then()
const middlewareAuthLink = new ApolloLink((operation, forward) => {
getSessionToken().then(token => {
const authorizationHeader = token ? `Bearer ${token}` : null
operation.setContext({
headers: {
authorization: authorizationHeader
}
})
});
return forward(operation)
})
This does construct the authorization string properly, but doesn't seem to create the middleware because this does not create the authorization header. To me, this seems like my best bet, but I must be doing it wrong for reasons which are not obvious to me.
POST / HTTP/1.1
Host: localhost:4000
Content-Type: application/json
Connection: keep-alive
Accept: */*
User-Agent: Expo/2.2.0.1011489 CFNetwork/893.14 Darwin/17.3.0
Content-Length: 174
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Notice the lack of Authorization header
2. async/await
I thought this would be pretty straight forward, turning this into an async/await function, but not so much. Here's the code
const middlewareAuthLink = new ApolloLink(async (operation, forward) => {
const token = await getSessionToken();
const authorizationHeader = token ? `Bearer ${token}` : null;
operation.setContext({
headers: {
authorization: authorizationHeader,
}
});
return forward(operation)
})
But that resulted in this super ugly red-screen-of-death error...
Unhandled (in react-apollo:Apollo(EventList)), ApolloError#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:106405:36
currentResult#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:106516:43
dataForChild#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101608:79
render#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101659:49
finishClassComponent#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:4528:102
performUnitOfWork#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5547:33
workLoop#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5566:142
_invokeGuardedCallback#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2707:23
invokeGuardedCallback#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2681:41
performWork#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5602:41
scheduleUpdateImpl#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5723:105
enqueueForceUpdate#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:4341:179
forceUpdate#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:8265:38
forceRenderChildren#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101579:58
next#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101554:50
error#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:109797:25
forEach#[native code]
error#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:106747:44
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107277:47
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107649:29
forEach#[native code]
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107648:27
forEach#[native code]
broadcastQueries#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107644:33
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107239:51
tryCallOne#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:10901:14
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:10987:25
_callTimer#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:12157:15
_callImmediatesPass#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:12193:17
callImmediates#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:12397:31
__callImmediates#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2301:30
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2179:32
__guard#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2287:11
flushedQueue#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2178:19
flushedQueue#[native code]
invokeCallbackAndReturnFlushedQueue#[native code]
So how do I wait for the result of AsyncStorage to return before setting the header?
There may be other solutions, or better solutions even, but this finally worked for me so I'm posting it as solved.
import { setContext } from 'apollo-link-context';
const httpLink = new HttpLink({ uri: 'http://localhost:4000/'});
const authHeader = setContext(
request =>
new Promise((success, fail) => {
getSessionToken().then(token => success({ headers: { authorization: `Bearer ${token}` }}))
})
)
const client = new ApolloClient({
link: concat(authHeader, httpLink),
cache: new InMemoryCache(),
})
Here is an alternative which loads the token from an in memory cache which should perform better than fetching from AsyncStorage. Alternatively you could simply have a getter wrapper function which caches the response from AsyncStorage but having this as part of apollo cache has the added benefit of re-rendering when you update the token.
import gql from 'graphql-tag'
import ApolloClient from 'apollo-client'
import { setContext } from 'apollo-link-context'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'
import AsyncStorage from '#react-native-community/async-storage'
const authLink = setContext(async (_, { headers }) => {
const context = await client.query({
query: gql`
query AuthQuery {
authToken #client
}
`
})
const {
data: { authToken: token }
} = context
let extra = {}
if (token) {
extra = { authorization: `Bearer ${token}` }
}
return {
headers: {
...headers,
...extra
}
}
})
const httpLink = createHttpLink({
uri: 'http://localhost:4000/'
})
const cache = new InMemoryCache()
const client = new ApolloClient({
link: ApolloLink.from([authLink, httpLink]),
cache
})
client.writeData({ data: { authToken: null } })
AsyncStorage.getItem('token').then((token: string | null) => {
if (token) {
client.writeData({ data: { authToken: token } })
}
})
export { client }