Ionic http requests with authorization headers - angularjs

I am sending a get request to the server and it requires a JWT token to authenticate. However Ionic insists on doing a pref-etch request without one and crashing. (Also is there any way to capture non 200 responses? the server gives a lot of those (e.g. 403 {message: Account Invalid}))
Code
auth.ts
import { Headers, RequestOptions } from '#angular/http'
import 'rxjs/add/operator/toPromise';
...
export const getToken = function(http){
return new Promise((resolve, reject) => {
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append('Authorization', 'JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4Yzg1MmI1YmQ1NjE1MGJkMDAxZWEzNyIsImlhdCI6MTQ4OTY3ODE0NywiZXhwIjoxNDg5NjgxNzQ3fQ.zUWvBnHXbgW20bE65tKe3icFWYW6WKIK6STAe0w7wC4');
let options = new RequestOptions({headers: headers});
http.get('//localhost:3000/auth/users', {headers: options})
.toPromise()
.then(res => resolve(res))
.catch(err => console.log(err));
});
}
Chrome console:
Response for preflight has invalid HTTP status code 401
Server sees: (I logged out the request and there are no headers or body)
OPTIONS /auth/users 401 25.613 ms - -

import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { Toast, Device } from 'ionic-native';
import { Http, Headers } from '#angular/http';
let headers = new Headers();
headers.append('Token', this.Token);
headers.append('id', this.ID);
this.http.get(this.apiUrl + this.yourUrl, { headers: headers })
.map(res => res.json())
.subscribe(
data => {
console.log(data);
if (data.code == 200) { // this is where u r handling 200 responses
if (data.result.length > 0) {
for (let i = 0; i < data.result.length; i++) {
var userData = {
username: data.result[i].username,
firstName: data.result[i].firstName,
lastName: data.result[i].lastName,
}
console.log(JSON.stringify(userData));
this.Results.push(userData);
}
}
}
else { // here non 200 responses
console.log(data.message);
}
this.user= this.Results;
console.log(this.user);
},
err => {
console.log("ERROR!: ", err);
});
this way u will be able to handle all responses from backend
I hope this works for you

To anyone else having this issue. devanshsadhotra's answer is great but here is the way I solved this issue:
ionic.config.json (add all the relevant routes here)
"proxies": [
{
"path": "/api",
"proxyUrl": "http://localhost:3000/api"
},
{
"path": "/auth",
"proxyUrl": "http://localhost:3000/auth"
}
]
Your networking file (auth.js in this case)
import { Headers } from '#angular/http' //Headers need to be in this object type
import 'rxjs/add/operator/toPromise'; //turns observable into promise
export const getToken = function(http){ //passing in the Http handler to the function for no good reason. but it works
return new Promise((resolve, reject) => { //return a promise to the calling function so it can handle the response
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append('Authorization', 'JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4Yzg1MmI1YmQ1NjE1MGJkMDAxZWEzNyIsImlhdCI6MTQ4OTY4MjY2MywiZXhwIjoxNDg5Njg2MjYzfQ.tW8nT5xYKTqW9wWG3thdwf7OX8g3DrdccM4aYkOmp8w');
http.get('/auth/users', {headers: headers}) //for post, put and delete put the body before the headers
.toPromise() //SANITY!!!
.then(res => resolve(res)) //Things went well....
.catch(err => console.log(err)); //Things did not...
});
}

Related

Axios Interceptor is not working in React JS

I am using the below code as an interceptor in my React JS app for getting token back but unfortunately, it is not working. Refresh token returns new idToken and updates local storage data correctly. The same code I'm using some other application which works fine. One main difference is that I currently use React 18 and the previous 16. I struggled to identify the problem but failed. Your help will be appreciable.
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response.status === 401) {
// console.log(error.response.data.code)
let usersData = JSON.parse(localStorage.getItem("userData"));
const refreshToken = usersData.refreshToken;
return axios
.post(
`${api_base_url}/auth/authentication/refresh_token`,
JSON.stringify({
refresh_token: refreshToken,
})
)
.then((response) => {
usersData["accessToken"] = response.data.data.accessToken;
usersData["idToken"] = response.data.data.idToken;
setSessionStorage("userData", usersData);
error.response.config.headers[
"Authorization"
] = `Bearer ${response.data.data.idToken}`;
return axios(error.response.config);
})
.catch((error) => {
if (error.response.data.code !== "TOKEN_EXPIRED") {
return;
}
localStorage.clear();
window.location = "/login";
});
}
return Promise.reject(error);
}
);
function getIRequestProp(severType, isMultipart, isSocial) {
const serverUrl = severType ? social_api_base_url : api_base_url;
let userData = JSON.parse(localStorage.getItem('userData'));
let idToken;
idToken = userData !== null ? userData['idToken'] : '';
let content_type;
if (isSocial) {
content_type = 'application/x-www-form-urlencoded'
} else {
content_type = isMultipart ? 'multipart/form-data' : 'application/json'
}
return {
serverUrl: serverUrl,
requestHeader: {
'Content-Type': content_type,
'Accept-Language': DEFAULT_LANGUAGE,
Authorization: `Bearer ${idToken}`
}
};
}
async function post(url, body, isSocialServer, isMultipart) {
const {serverUrl, requestHeader} = getIRequestProp(isSocialServer, isMultipart);
return axios.post(serverUrl + url, body, {
headers: requestHeader
});
}
So, I call API like this:
AxiosServices.post(ApiUrlServices.SOCIALS_UPDATE_LINKS(UserInfo.userId), payload, false)
.then(response => {})
What i figured out that return axios(error.response.config); is not sending authorization token in API request headers and trying request infinitely. But consoling error.response.config shows token sets in the config correctly.
Adding an additional modification of axios request, I solved my problem.
axios.interceptors.request.use(request => {
// Edit request config
let usersData = JSON.parse(localStorage.getItem('userData'));
request.headers['Authorization'] = `${usersData.idToken}`;
return request;
}, error => {
return Promise.reject(error);
});

react admin returns Unauthorized 401 error upon CRUD operations

I am working on a react-admin project. The backend is written using Django rest framework which runs on a docker container. The authentication endpoints for access and refresh tokens are written using djangorestframework-simplejwt and served at http://localhost:8000/api/token/ and http://localhost:8000/api/token/refresh/ respectively.
I have written my own authProvider.js and dataProvider.js for react admin. The login and checkAuth functions for authProvider.js looks like this
// in src/authProvider.js
import jwt from "jsonwebtoken";
export default {
login: async ({ username, password }) => {
const request = new Request('http://localhost:8000/api/token/', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
const response = await fetch(request);
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
const { refresh, access } = await response.json();
localStorage.setItem('refreshToken', refresh);
localStorage.setItem('accessToken', access);
},
logout: ...
checkAuth: async () => {
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
if (accessToken && refreshToken) {
const { exp } = await jwt.decode(accessToken);
if (exp > (new Date().getTime() / 1000) - 10) {
return Promise.resolve();
} else {
const request = new Request('http://localhost:8000/api/token/refresh/', {
method: 'POST',
body: JSON.stringify({ "refresh": refreshToken }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
const response = await fetch(request)
.then(response => {
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token }) => {
localStorage.setItem('accessToken', token);
return Promise.resolve();
});
return response;
}
}
return Promise.reject();
},
checkError: ...
getPermissions: () => Promise.resolve(),
}
Retrieving data works fine. But whenever I perform a create, edit and delete operation, I am automatically logged out with a 401 Unauthorized error. Error message from docker server log
Unauthorized: /api/products/2
"PUT /api/products/2 HTTP/1.1" 401
Error from browser console: PUT HTTP://localhost:8000/api/products/2 401 (Unauthorized)
Prior to adding authProvider and using docker container as backend, CRUD data mutations worked fine, using a local python venv as backend. So I assume the dataProvider.js is not responsible here.
I have not been able to figure this out for quite some time. Can anyone help me figure out what I might be doing wrong here? Thank you for your time.
EDIT 1: It seems the access token is not sent from the frontend during API request, hence the server returning 401 Unauthorized
You need to modify your dataProvider to include the token (in a token, a cookie, or in a GET parameter, depending on what your backend requires). This is explained in the react-admin auth documentation:
import { fetchUtils, Admin, Resource } from 'react-admin';
import simpleRestProvider from 'ra-data-simple-rest';
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
const { token } = JSON.parse(localStorage.getItem('auth'));
options.headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, options);
};
const dataProvider = simpleRestProvider('http://localhost:3000', httpClient);
const App = () => (
<Admin dataProvider={dataProvider} authProvider={authProvider}>
...
</Admin>
);

Resending a request that was made with an expired token is leading to status pending in developer tools

I have a react application where I am trying to implement JWT.
I am using the axios interceptor where I catch status 401 returned by the server due to expired token, send the refresh token to server, receive the new access token in the client and then resend the original failed request.
The problem I am facing is that, when I resend the original failed request, the status appears as pending forever in the developer tools, network tab. The original failed request is a POST request, when I checked the database it was updated. So why is it showing pending status in the developer tools ?
Here is my axios interceptor code
import axios from 'axios'
// import refreshToken from '../src/Store/refreshToken'
import { store } from '../src/index'
import { removeAuth } from '../src/Store/actions/authAction'
const api = axios.create({
baseURL: process.env.REACT_APP_SERVER
})
function createAxiosResponseInterceptor(axiosInstance) {
axiosInstance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if (token){
config.headers.Authorization = token;
}
return config
}
)
axiosInstance.interceptors.response.use(
response => {
return response;
},
error => {
var errorStatus = error.response.status;
if (errorStatus === 401){ // status 401 is used when token is expired
let cookies = document.cookie
let refresh = cookies.split("refresh=")[1].split(';')[0]
if(!sendRefreshToken(refresh, error)) {
store.dispatch(removeAuth({isLoggedIn: false}));
localStorage.setItem('token', '');
document.cookie = "refresh=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}
}
return error
}
);
}
function sendRefreshToken(refreshToken, error) {
let result = api.post('/refresh', {
refreshToken: refreshToken
})
.then(response => {
if (response.data.success && response.data.message === "new access token set") {
localStorage.setItem('token', response.data.newToken)
api({ // Here I am resending the failed request.
method: error.response.config.method,
url: error.response.config.url,
data: JSON.parse(error.response.config.data)
}).then(response => {
console.log(response)
return true
})
.catch(error => {
console.log(error)
return false
})
}
})
.catch(error => {
console.log(error)
return false
})
return result
}
createAxiosResponseInterceptor(api);
export default api;
Please let me know if you find anything wrong with the code. Let me know if this is the right way to do it. Open to offer more bounty points.
Consider this article for reference.
https://medium.com/swlh/handling-access-and-refresh-tokens-using-axios-interceptors-3970b601a5da
import axios from 'axios'
// import refreshToken from '../src/Store/refreshToken'
import { store } from '../src/index'
import { removeAuth } from '../src/Store/actions/authAction'
const api = axios.create({
baseURL: process.env.REACT_APP_SERVER
})
function createAxiosResponseInterceptor(axiosInstance) {
axiosInstance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if (token){
config.headers.Authorization = token;
}
return config
}
)
axiosInstance.interceptors.response.use(
response => {
return response;
},
error => {
var errorStatus = error.response.status;
const originalRequest = error.config;
if (
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
return api
.post('/refresh', {
refreshToken: getRefreshToken()
})
.then((jsonRefreshResponse) => {
if (jsonRefreshResponse.status === 200) {
// 1) put token to LocalStorage
saveRefreshToken(
jsonRefreshResponse.data.refreshToken
);
// 2) Change Authorization header
const newAccessToken = getJwtToken();
setAuthHeader(newAccessToken);
// 3) return originalRequest object with Axios.
// error.response.config.headers[
// "Authorization"
// ] = `Bearer ${newAccessToken}`;
setAuthHeader(newAccessToken)
return axios(error.response.config);
}
})
.catch((err) => {
console.warn(err);
})
}
if (error.config) {
console.log(error.config);
return Promise.reject();
}
}
);
}
export const setAuthHeader = (token) => {
api.defaults.headers.common["Authorization"] = `Bearer ${token}`;
};
createAxiosResponseInterceptor(api);
export default api;
//These methods could be in separate service class
const getJwtToken=()=> {
return localStorage.getItem("token");
}
const getRefreshToken=() =>{
return localStorage.getItem("refreshToken");
}
const saveJwtToken=(token)=> {
localStorage.removeItem("token");
localStorage.setItem("token", token);
}
const saveRefreshToken=(refreshToken)=> {
localStorage.setItem("refreshToken", refreshToken);
}

How to assign headers in axios.all method React Native

I'm getting data from multiple api's and rendering into <Pickers /> but the issue is that I'm unable to assign headers for Auth in this axios.all method. Kindly provide me a solution or mistake if I'm doing.
axios.all([
axios.get(this.apiUrl + '/case/GetCaseType'),
axios.get(this.apiUrl + '/case/GetCasePriority')
], { headers: { 'authorization': 'bearer ' + this.state.jwtToken } })
.then(axios.spread(( response2, response3) => {
console.log('Response 1: ', response1.data.retrn);
console.log('Response 2: ', response2.data.retrn);
console.log('Response 3: ', response3.data.retrn);
this.hideLoader();
})).catch(error => console.log(error.response));
You can create a service file to handle all request for GET or POST, FIle for handling GET request with Autherization header is given belwo
GetService.js
import axios from 'axios';
let constant = {
baseurl:'https://www.sampleurl.com/'
};
let config = {
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
}
};
export const GetService = (data,Path,jwtKey) => {
// jwtKey is null then get request will be send without header, ie for public urls
if(jwtKey != ''){
axios.defaults.headers.common['Authorization'] = 'Bearer '+jwtKey;
}
try{
return axios.get(
constant.baseUrl+'api/'+Path,
data,
config
);
}catch(error){
console.warn(error);
}
}
You can use the configs and create instance to set common things like url, token, timeout etc
import axios from 'axios';
const http = axios.create({
baseURL: this.url,
timeout: 5000
});
http.defaults.headers.common['authorization'] = `bearer ${this.state.jwtToken}`
export async function yourAPIcallMethod(){
try{
const [res1,res2] = await http.all([getOneThing(), getOtherThing()]);
//when all responses arrive then below will run
//res1.data and res2.data will have your returned data
console.log(res1.data, res2.data)
//i will simply return them
return {One: res1.data, Two: res2.data}
}
catch(error){
console.error(error.message || "your message")
}
}
This can be used in your component.jsx like
import {yourAPIcallMethod} from './httpService';
async componentDidMount(){
const data = await yourAPIcallMethod();
this.setState({One: data.One, Two: data.Two})
}
You can see and learn more on github axios.

axios.post returns bad request of 400 React Native

I'm getting my token from an API but unfortunately my API is returning 400 bad request. I've already checked my api via Postman and it's working fine there. Kindly let me know solution or any mistake.
async componentWillMount(){
axios.post('http://api.myapiurl.com/token', {
grant_type: 'PASSWORD',
username: 'MY_USERNAME',
password: 'MY_PASSWORD'
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(response => {
console.log(response.data)
}).catch(err => console.log("api Erorr: ", err.message))
}
error in response below
Request failed with status code 400
- node_modules\axios\lib\core\createError.js:16:24 in createError
- node_modules\axios\lib\core\settle.js:18:6 in settle
- ... 10 more stack frames from framework internals
It is Solved by using QueryString.stringify(). I just pass the body into QueryString.stringify() like below:
axios.post('http://api.apiurl.com/token', QueryString.stringify({
grant_type: 'MY_GRANT_TYPE',
username: 'MY_USERNAME',
password: 'MY_PASSWORD'
}), {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
}
}).then(response => {
console.log(response.data)
}).catch(err => console.log("api Erorr: ", err.response))
From what I can see you are sending json data, but your Content-Type header is set to application/x-www-form-urlencoded; charset=UTF-8. if your api is expecting json then it should be application/json.
try using fetch instead, might be some axios bug, you dont need to add any libraries, here is an example:
fetch("http://api.myapiurl.com/token", {
method: "POST", // *GET, POST, PUT, DELETE, etc.
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
grant_type: "PASSWORD",
username: "MY_USERNAME",
password: "MY_PASSWORD"
})
})
.then(res => {
res.json();
})
.then(data => console.log(data)) // ur data is here
.catch(err => console.log("api Erorr: ", err));
First install the package axios from the url https://www.npmjs.com/package/react-native-axios
Then create two service for handling get and post request so that you can reuse them
GetService.js
import axios from 'axios';
let constant = {
baseurl:'https://www.sampleurl.com/'
};
let config = {
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
}
};
export const GetService = (data,Path,jwtKey) => {
if(jwtKey != ''){
axios.defaults.headers.common['Authorization'] = 'Bearer '+jwtKey;
}
try{
return axios.get(
constant.baseUrl+'api/'+Path,
data,
config
);
}catch(error){
console.warn(error);
}
}
PostService.js
import axios from 'axios';
let constant = {
baseurl:'https://www.sampleurl.com/'
};
let config = {
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
}
};
export const PostService = (data,Path,jwtKey) => {
if(jwtKey != ''){
axios.defaults.headers.common['Authorization'] = 'Bearer '+jwtKey;
}
try{
return axios.post(
constant.baseUrl+'api/'+Path,
data,
config
);
}catch(error){
console.warn(error);
}
}
Sample code for using get and post services is given below
import { PostService } from './PostService';
import { GetService } from './GetService';
let uploadData = new FormData();
uploadData.append('key1', this.state.value1);
uploadData.append('key2', this.state.value2);
//uploadData.append('uploads', { type: data.mime, uri: data.path, name: "samples" });
let jwtKey = ''; // Authentication key can be added here
PostService(uploadData, 'postUser.php', jwtKey).then((resp) => {
this.setState({ uploading: false });
// resp.data will contain json data from server
}).catch(err => {
// handle error here
});
GetService({}, 'getUser.php?uid='+uid, jwtKey).then((resp) => {
// resp.data will contain json data from server
}).catch(err => {
// handle error here
});
Reference from one of my another post Post action API with object parameter within the URL
If you have any doubts, feel free to know

Resources