Reading token from AsyncStorage before a fetch call - javascript

In my ReactNative app, I'm trying to come up with a nice pattern to read the access_token I store in AsyncStorage and use it in a fetch call.
In other words, I want to create a pattern that uses some type of wrapper that makes sure that the fetch call always has the access_token it needs. So execution order should always be:
Invoke Fetch Call -> Get Token from AsyncStorage and Prep Header -> Execute Fetch Call
I came up with the following code but it looks like I'm having problems with the Async part of AsyncStorage and my fetch calls are going out without the token.
Here's my fetch call:
export const someApiCall = (request) => {
const url = 'https://myapi.com/add';
return (dispatch) => fetch(url, fetchOptionsPost(request))
.then((response) => {
if (response.ok && response.status === 200) {
// Got data. Dispatch some action
}
})
}
Here, I'm using a helper function to prepare the headers, etc. Here's what the fetchOptionsPost() looks like:
export const fetchOptionsPost = (request) => {
getAccessToken()
.then(token => {
return {
method: 'POST',
mode: 'cors',
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
body: JSON.stringify(request)
}
});
};
And the getAccessToken() function simply reads it from AsyncStorage as below:
export const getAccessToken = async () => {
return await AsyncStorage.getItem("access_token");
}
This pattern is NOT working and API calls are going out without a token.
I also want to mention that if I hard-code the token inside my fetchOptionsPost() method, everything works fine. Clearly, the issue here is that the fetchOptionsPost() is not returning anything.
What can I do to make sure that I will ALWAYS have my token in my fetchOptionsPost?

you could add token call inside someApiCall function .And also create the options on inside the function . function was async so fetch only run after get token result
Updated
const fetchOptionsPost = (token) =>{
return ({
method: 'POST',
mode: 'cors',
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
}
})
}
export const someApiCall = async(request) => {
const token = await AsyncStorage.getItem("access_token");
const url = 'https://myapi.com/add';
fetch(url, fetchOptionsPost(token))
.then(response=>response.json())
.then((data)=>{
// call the dispatch here
})
}
Why use async inside on main function
AsyncStorage only have async callback. if you are set the AsyncStorage as separate function you should call async for the both function.Thats why i added with in parent async function and pass the token to fetchOptionsPost

Here's what I've come up with which seems to work fine. I still would appreciate any suggestions or improvements to this code.
First, here's what my fetch call looks like now. I wrapped it inside the getAccessToken() function which is an async call but because I'm using redux-thunk, I'm able to do this.
export const someApiCall = (request) => {
const url = 'https://myapi.com/add';
return (dispatch) => getAccessToken()
.then(token => {
fetch(url, fetchOptionsPost(request, token))
.then((response) => {
if (response.ok && response.status === 200) {
// Got data. Dispatch some action
}
})
})
}
I slightly changed my fetchOptionsPost() helper function which now accepts the token. It's also a bit more robust now. If it doesn't get a token, it simply omits the Authorization part in the header. I opted for this approach as some calls to my API backend do not require authentication. Also the isValidString() is another helper validation function I created to make sure I do get a valid string. It returns a TRUE or FALSE response based on the string value inputed:
export const fetchOptionsPost = (data, token = null) => {
if (isValidString(token)) {
return {
method: 'POST',
mode: 'cors',
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
body: JSON.stringify(data)
}
} else {
return {
method: 'POST',
mode: 'cors',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}
}
};
And finally, the getAccessToken() function which didn't really change but here it is:
export const getAccessToken = async () => {
return await AsyncStorage.getItem("access_token");
}
As I said, I'd appreciate any comments or suggestions on further improving this code.
Hope this is useful to others.

Use Async and await method and get the token before each call.
async ()=>{
let token =await getTokenFromLocal();
return (dispatch) => fetch(url, fetchOptionsPost(request))
.then((response) => {
if (response.ok && response.status === 200) {
// Got data. Dispatch some action
}
})
}

Related

How to use POST api token to use third party APIs in nextjs

I need to POST to a third party api route to receive an access token to be authorized to fetch data from their endpoints, but I'm not sure how to use the token. They are using Swagger:
export default async function() {
const res = await fetch('url', {
method:'POST',
headers: {
accept: 'application/json'
}
});
const data = await res.json()
console.log(data)
}
I get in response:
{
access_token: 'my token...',
...
}
But I'm not sure how I'd use this response to authorize fetching data. Do I need to pass the token to the headers in the fetch?
export async function getStaticProps() {
const res = await fetch( `url`, {
headers: {
accept: 'application/json',
}
});
const data = await JSON.stringify(res);
return {
props: {
items: data
},
};
}
I can't seem to find much info on this, or I'm just searching for the wrong things. I'm not sure if I'm understanding this correctly
Likely you'll need to send that token in an Authorization header with the request, something like this:
const res = await fetch(`url`, {
headers: {
accept: 'application/json',
Authorization: `Bearer ${token}`,
}
});
const json = await res.json()
const data = JSON.stringify(json);
The API docs should be able to tell you specifics though, if you're still having issues please post the API docs if they're public!

Vue send request until get result from another one

I'm from Ruby language so sorry for noob question or if my concept is wrong - please tell me.
In my Vue application user should provide some data and asynchronously get the result below the form. The flow is like:
user provides input data
the app sends POST request (createProductsRequest) to Rails backend app
Vue get response with load_id which represents id of newly created record e.g. 12345 - sample json: { load_id: 12345 }
Vue app use load_id and send GET request (fetchSyncedProductsResultRequest) to Rails backend app endpoint (sample json: {result: [{test: 'test'}]})
check if response.result is nil ({result: nil}), if yes resent request until it will not be nil
display response data
The question is where (and how actually) to put the loop from step 5 that checks if a given response from step 4 does not contain null? Vue should stop sending requests when response is not nil.
Here's what I've produced so far:
import.js
const createProductsRequest = (self, products) => {
const jwtToken = self.$store.state.idToken;
const payload = JSON.stringify({ product_codes: products['product_codes'].split(',') })
return axios
.post(`/api/v1/imports/products_batches`, payload,{
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(response => response.data)
};
const fetchSyncedProductsResultRequest = (token, id) => {
return axios
.get(`/api/v1/imports/products_batches`, {
params: { id: id },
headers: {
Authorization: `Bearer ${token}`,
}
})
.then(response => {
return response.data['result']
})
};
sync_products.vue
<template>
<div class="col-12 col-md-3">
<button
type="button"
class="btn btn-primary"
#click="syncProducts"
>
Sync
</button>
</div>
</template>
<script>
import {
fetchSyncedProductsResultRequest,
createProductsRequest
} from '../../api/imports'
export default {
name: 'SyncProducts',
data() {
return {
fetchedProductSyncStatus: [],
load_id: ''
}
},
async mounted() {
await fetchSyncedProductsResultRequest(this, id)
this.syncedProductsFetched = true
this.pageChanged(this.currentPage)
},
async mounted() {
const jwtToken = this.$store.state.idToken;
fetchSyncedProductsResultRequest(jwtToken).then(data => {
this.fetchedProductSyncStatus = data
})
},
methods: {
async syncProducts() {
let confirmationText = `Do you want to ${this.productsToSyncAmount} sync products?`
if (this.productsToSyncAmount === 0) {
ModalController.showToast('', 'Type product codes for sync first, please!', 'warning')
}
else if (await ModalController.showConfirmation('Confirmation', confirmationText)) {
try {
ModalController.showLoader()
await createProductsRequest(this, this.styleCodes)
const successMessage = `${this.productsToSyncAmount} products have been queued for sync`
await ModalController.showToast('', successMessage)
} catch (data) {
const errorMessage = `Error occurred during queueing products to sync - `
ModalController.showToast('', errorMessage + data?.message, 'error')
} finally {
this.styleCodes = []
ModalController.hideLoader()
}
}
},
}
}
</script>
To spare your backend i'd probably wait x amount of time (enough for the backend to have created the resource) and then send the get request - instead of potentially spamming it.
With that said i think you want to use the setTimeout function to call a function that makes the API call. There you can make the call, check if result is nil and then use setTimeout and call the function again if needed. Like so:
async loadId() {
const data = await makeApiRequest()
if (!data.result) {
setTimeout(this.loadId, waitTimeInMillis);
return;
}
//Do what you want if when result isn't null.
}

How do I add a JS state variable of 1 React component for use in other components?

I have a Home.js component that signs the user up to the API and logs in and then gets the token received from the response authorization header and saves it in the state 'token' variable.
This token will be used in all other components to access the API when requests are made, so what is the best way of using this value for all other components?
Home.js:
const SIGNUP_URL = 'http://localhost:8080/users/signup';
const LOGIN_URL = 'http://localhost:8080/login';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated:false,
token: ''
};
}
componentDidMount() {
const payload = {
"username": "hikaru",
"password": "JohnSmith72-"
};
fetch(SIGNUP_URL, {
method: 'POST',
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then((data) => {
console.log(data);
});
fetch(LOGIN_URL, {
method: 'POST',
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(response =>
this.setState({token: response.headers.get("Authorization"), isAuthenticated:true})
)
}
For example the userList component which will fetch the user data from the API, but requires the API token stored in the Home component's token state variable to send the request successfully via the authorization header.
Thanks for any help
You can create a custom function called authenticated_request for example. This function could fetch your token from the CookieStorage in case of web or async storage in case of react-native or even if you have it in some state management library. Doesn't matter. Use this function instead of the fetch function and call fetch inside it. Think of it as a higher order function for your network requests.
const authenticated_request(url, config) {
fetch(url, {
...config,
headers: {
...config.headers,
Authorization: getToken()
}
});
}
You can also leverage the usage of something like axios and use request interceptors to intercept requests and responses. Injecting your token as needed.
You should be using AuthContext and localStorage to do this, save the token in the state or localStorage and make a config file which uses the same token when calling an api i have done it in axios. Axios has a concept of interceptors which allows us to attach token to our api calls, Im saving the token in the localStorage after a successfull login and then using the same token from localStorage to add to every call which needs a token, if the api doesnt need a token (some apis can be public) i can use axios directly, check out the below code:
import axios from 'axios';
let apiUrl = '';
let imageUrl = '';
if(process.env.NODE_ENV === 'production'){
apiUrl = `${process.env.REACT_APP_LIVE_URL_basePath}/web/v1/`;
}else{
apiUrl = `http://127.0.0.1:8000/web/v1/`;
}
const config = {
baseURL: apiUrl,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "https://www.*******.com",
'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE',
},
};
const authAxios = axios.create(config);
authAxios.interceptors.request.use(async function(config) {
config.headers.Authorization = localStorage.getItem('access_token') ?
`Bearer ${localStorage.getItem('access_token')}` :
``;
return config;
});
export { apiUrl, axios, authAxios };
now on making api call u can do something like below:
import { apiUrl, authAxios } from '../config/config'
export async function saveAssignment(data) {
try {
const res = await authAxios.post(apiUrl + 'assignment/save-assignment', data)
return res.data;
}
catch(e){
}
}
here pay attention im not using axios to make api call but using authAxios to make calls(which is exported from the config file) which will have token in the header.
(You can also use a third party library like Redux but the concept remains the same)
You need a centralized state that's what State Management libraries are for. You can use third-party libraries such as Redux, or simply use React's own context. You can search on google for state management in React and you'll find a lot of helpful recourses
You can place the token into a cookie if your app is SSR. To do that, you have to create the following functions:
export const eraseCookie = (name) => {
document.cookie = `${name}=; Max-Age=-99999999;`;
};
export const getCookie = (name) => {
const pairs = document.cookie.split(';');
const pair = pairs.find((cookie) => cookie.split('=')[0].trim() === name);
if (!pair) return '';
return pair.split('=')[1];
};
export const setCookie = (name, value, domain) => {
if (domain) {
document.cookie = `${name}=${value};path=/`;
} else {
document.cookie = `${name}=${value}`;
}
};
You can also place your token into the local storage:
Set into local storage via built-in function:
localStorage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c');
Get the token via built-in function:
const token = localStorage.getItem('token');

Typescript removes Authorization header from POST and PATCH fetch requests

I've built an API using C# that uses JWT tokens for authorization. On the frontend I store these tokens in local storage and get them, when creating a request. When creating GET or DELETE requests, everything works fine, and using console.log() I can see that fetch options have the Authorization header added. However when using POST or PATCH methods, the Authorization header is missing immediatly after adding it to the object. Here is my request method:
const send = async (apiOptions: ApiParams): Promise<FetchReturn> => {
const accessToken = GetAccessToken()
const options: ApiOptions = {
method: apiOptions.method,
headers: {
Authorization: `Bearer ${accessToken}`
}
}
console.log(options)
if (apiOptions.data) {
options.headers = {
'Content-Type': 'application/json'
}
options.body = JSON.stringify(apiOptions.data)
}
const result = await fetch(`${getUrl()}/${apiOptions.path}`, options).then(res => res).catch(err => err)
if (!result.ok) {
if (IsExpired()) {
const refreshResult = await fetch(`${getUrl()}/api/user/refresh`, {method: 'POST', headers:{
'Content-Type': 'application/json'
}, body: JSON.stringify(GetRefreshRequest())}).then(res => res).catch(err => err)
if (refreshResult.ok) {
Login(JSON.parse(await refreshResult.text()))
return await send(apiOptions)
} else if (refreshResult.status === 401) {
Logout()
window.location.reload()
return { code: 0, text: ""}
}
}
}
const text = await result.text()
return { code: result.status, text: text }
}
I suppose that in apiParams for POST you have property 'data' assigned, and later you have if-condition that completely replaces request headers object.
Change it to:
options.headers['Content-Type'] = 'application/json';
To keep authorization in headers
The first time check your apiOptions.data
i think , its null when you call POST/Patch request
Just put console.log("...") In the if statement , Then try for resolve your Error
If your problem not resolved, put a replay under my post

React-native async fetch returns null

I am trying to put fetch functions into a separated file, so I can organise these API fetch easily. However, when I try to fetch and return the data, it gives me null or an unexpected json object. Here is part of my src:
//api.js
export async function LoginAPI(username, password) {
const url = baseURL + "/login/";
var params = {username: username, password: md5.hex_md5(password)};
let response = await fetch(url, {
method: 'POST',
headers: {'Accept': 'application/json','Content-Type': 'application/x-www-form-urlencoded'},
body: JSON.stringify(params)
});
return await fetch(url, {
method: 'POST',
headers: header,
body: JSON.stringify(params)
})
.then((res) => res.text())
.then((text) => text.length ? JSON.parse(text) : {})
.catch((error) => {
throw error;
});
};
Here is the another file.
//index.js
var Login = React.createClass({
onPress_login: function() {
var value = this.refs.form.getValue();
if (value) {
result = LoginAPI(value.username, value.password);
console.log(result);
} else {
this.AlertDialog.openDialog();
}
},
render() {
return (
(...)
<Button onPress={this.onPress_login}>
<Text>Login</Text>
</Button>
The fetch is working, it is communicating with the server. However, the console log returns me this at the first place
Promise _45: 0_54: null _65: null _81: 1 __proto__: Object
I am assuming that the result log in the console at the first place is not the await result (the actual response from server, but an object of fetch response). I tried to search out methods online but I can't find any post/blog/article saying how to do fetch as a function call.
Is there any way to do like swift, LoginAPI(username, password, callback: {...}) please?
The problem is that you're are making an async function and not waiting for the response, the you see that kind of console log.
Try this:
result = await LoginAPI(value.username, value.password);
Let me know if this was your problem.

Categories

Resources