I'm working on a Vue app that uses Axios and Wordpress JWT Auth for authentication. Recently I started getting this 401 when trying to post (100% of the time) ...but... only on Mac OS X and iOS.
Can you point me in the right direction as to how to troubleshoot this? The error object in Chrome console network is this:
{"code":"rest_forbidden","message":"Sorry, you are not allowed to do that.","data":{"status":401}}
My post looks like this:
https://wordpressroot.ourdomain.com/wp-json/jwt-auth/v1/folder/transfer {location_id: "rec4ttKVWJCDr8v", items: Array(1)}
axios localClient:
import axios from "axios";
import environment from "#/environments/environment";
import state from "../store";
import router from "../router";
const userData = JSON.parse(localStorage.getItem("userData"));
let instance = {};
if (userData) {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL,
headers: { Authorization: `Bearer ${userData.token}` }
});
} else {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL
});
}
instance.interceptors.request.use(
config => {
state.commit("setNetworkStatus", true);
return config;
},
error => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
state.commit("setNetworkStatus", false);
return response;
},
error => {
if ([401, 403].includes(error.response.status)) {
console.log(error);
state.commit("delUserData");
router.push("/login");
}
return Promise.reject(error);
}
);
export default {
get(path) {
return instance.get(instance.defaults.baseURL + path);
},
post(path, params) {
console.log(instance.defaults.baseURL + path, params);
return instance.post(instance.defaults.baseURL + path, params);
},
put(path, params) {
return instance.put(instance.defaults.baseURL + path, params);
},
delete(path, params) {
return instance.delete(instance.defaults.baseURL + path, params);
}
};
Related
I am getting this Websocket failed to Connect error for both client and server side now (as shown in the image below). I am not using any other Websocket configuration other than the one specified in the apollo client. This has been baffling me for about 2 days. Any help would be appreciated. Let me know if you need to see any further code.
I have a Vue app client that connects to graphql apollo server. The code for apolloclient configuration is given below.
// Apollo packages
import { ApolloClient } from "apollo-boost-upload";
import { WebSocketLink } from "apollo-link-ws";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { split } from "apollo-link";
import { getMainDefinition } from "apollo-utilities";
import VueApollo from "vue-apollo";
Vue.use(VueApollo);
wsLink = new WebSocketLink({
uri: "ws://localhost:4000/graphql", // use wss for a secure endpoint
options: {
reconnect: true,
},
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
// Cache implementation
export const defaultClient = new ApolloClient({
// uri: "http://localhost:4000/graphql",
link,
cache: new InMemoryCache(),
fetchOptions: {
credentials: "include",
},
request: (operation) => {
// if no token in local storage, add it
if (!localStorage.anaceptToken) {
localStorage.setItem("anaceptToken", "");
}
// operation adds the token to authorizatrion header, which is sent o backend
operation.setContext({
headers: {
authorization: "Bearer " + localStorage.getItem("anaceptToken"),
},
});
},
onError: ({ graphQLErrors, networkError }) => {
if (networkError) {
console.log("[networkError]", networkError);
}
if (graphQLErrors) {
for (const error of graphQLErrors) {
console.dir(error);
console.log(error);
if (
error.name === "AuthenticationError" ||
error.message === "jwt expired"
) {
// set auth error in state
store.commit("setError", error);
// signout user to clear error
store.dispatch("signUserOut");
}
}
}
},
});
vue config file
const { defineConfig } = require("#vue/cli-service");
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
module.exports = defineConfig({
pluginOptions: {
apollo: {
enableMocks: true,
enableEngine: true,
},
},
transpileDependencies: ["vuetify"],
chainWebpack: (config) => {
config.performance.maxEntrypointSize(400000).maxAssetSize(400000);
new NodePolyfillPlugin();
},
});
interesting try localhost 4004, it should work
import {
createClient,
defaultExchanges,dedupExchange, cacheExchange, fetchExchange,
subscriptionExchange,
gql
} from "#urql/core";
import { createClient as createWSClient } from "graphql-ws";
import { pipe, subscribe } from "wonka";
import { getToken, setToken } from "./helper";
const wsClient = createWSClient({
url: 'wss://**********/subscriptions',
reconnect: true,
});
const client = createClient({
url: "https://***********/",
fetchOptions: () => {
const token = getToken()
return token ? { headers: { authorization: `Bearer "${token}"` } } : {}
},
// the default:
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription(operation) {
return {
subscribe: (sink) => {
const dispose = wsClient.subscribe(operation, sink);
return {
unsubscribe: dispose,
};
},
};
},
}),
]
});
SUB_TO_MESSAGES = async () => {
console.log('sub')
const token = getToken();
console.log(String(token))
const { unsubscribe } = pipe(
await client.subscription(messageAdded,{ jwt: token }),
subscribe((result) => {
console.log(result)
})
)
};
I dont get the same issue with try and catch using GraphQL-WS but I still dont get any data from the server. The assignment is a vanillaJS project using GraphQL.I didndt post the url, jwt token,or the GET, POST, REgG as they work as intended. The rendering is done with a proxy. The error message is:
Connection Closed: 4500 Cannot read properties of undefined (reading 'Authorization')
Even playground doesnt work. Something wrong with the endpoint. It worked 2 weeks ago but admin says it still work yet I can find the problem. It used to work for me.
Here is the try and catch version:
import { createClient} from "graphql-ws";
import pStore from "./handler.js";
import { getToken } from "./helper";
const client = createClient({
url: "wss://******/subscriptions",
reconnect: true,
connectionParams:{
headers: {
"Authorization":`Bearer ${getToken()}`
}
},
})
async SUB_MESSAGE() {
try {
console.log('called Gql server')
const onNext = (res) => {
let obj = res.data.messageAdded
console.log(obj)
pStore[obj.id] = obj
pStore.render(obj)
};
let unsubscribe = () => {
/* complete the subscription */
};
new Promise((resolve, reject) => {
client.subscribe({
query: `subscription{messageAdded(jwt:"${getToken()}"){id text fromAgent createdAt updatedAt}}`,
},
{
next: (data)=> onNext(data),
error: reject,
complete: () => resolve(true),
})
})
}catch(error){
console.error('There has been a problem with your ws operation:', error);
}
}
Either way I think its a ad character, scope issue but I dont know where.
I have all my functions based views on django protected with #permission_classes([IsAuthenticated]) so I have to send a JWT as Bearer token on every request.
In the first version I was using this code:
import axios from 'axios';
import { decodeUserJWT } from '../../extras'
const user = JSON.parse(localStorage.getItem("user"));
var decoded = decodeUserJWT(user.access);
var user_id = decoded.user_id
const instance = axios.create({
baseURL: 'http://localhost:8000/api',
headers: {Authorization: 'Bearer ' + user.access},
params: {userAuth: user_id}
});
export default instance;
Everything was working fine.
But then I added interceptors so I could handle the refreshToken process:
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
// const uid = await decodeUserJWT(token);
config.headers["Authorization"] = 'Bearer ' + token;
// config.headers["userAuth"] = uid;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const { dispatch } = store;
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/token/obtain/" && err.response) {
console.log("TOKEN INTERCEPTOR");
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/token/refresh/", {
refresh: TokenService.getLocalRefreshToken(),
});
const { access } = rs.data;
dispatch(refreshToken(access));
TokenService.updateLocalAccessToken(access);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
What happens?
When I add the line config.headers["userAuth"] = uid; the django server console starts showing up that when the react app tries to access the routes it gets a Not Authorized, and when I take that line off de code ... it works fine.
I also tried to pass the param userAuth in the axios.create and keep only the Bearer config inside the interpector code, but still no positive result, the code with the interpector code only works when I take off the userAuth line from axios.
Any ideia on why this is happening and how can I fix this?
I have a strange situation with a React Native app.
The part of the code with problems is this:
const request = async (options) => {
const defaults = {baseURL: 'base URL'}
let token = await AsyncStorage.getItem('token');
console.log('this is logged')
if(token) {
console.log("this is logged")
const headers = {
'TokenAuth': token
}
Object.assign(defaults, headers: headers);
}
console.log('this is NOT logged anymore')
options = Object.assign({}, defaults, options);
};
The idea is that i can't see anywhere the javascript error.
The error is on Object.assign(defaults, headers);
Why i can't see it ?
Thank you.
This is the whole component:
import constants from './../constants/constants';
import axios from 'axios';
import { AsyncStorage } from 'react-native';
import * as Utils from '../configs/utils'
const request = async (options) => {
const defaults = {baseURL: constants.BASE_URL}
let token = await AsyncStorage.getItem('token');
if(token) {
const headers = {'TokenAuth': token}
Object.assign(defaults, {headers: headers});
}
options = Object.assign({}, defaults, options);
return axios(options)
.then(response => {
return response.data
} )
.catch( error => {
if (error.response.status == 401) {
Utils.deleteToken();
}
let errResponse = 'Bad Error'
throw errResponse;
});
};
export function getAllTodos() {
return request({method: 'get',
baseURL: constants.BASE_URL,
url: '/api/items',
})
}
I'm using aurelia-http-client and struggling to both use interceptors and set headers for my requests.
What I need to achieve is;
Each Interceptor (request, request error, response, and response error) emits an event using aurelia-event-aggregator when it's triggered.
A header is added to each request containing information entered on the page
The only way I've got interceptors to correctly publish events is to use aurelia.container in main.js like below;
import {HttpClient} from 'aurelia-http-client';
import {EventAggregator} from 'aurelia-event-aggregator';
export function configure(aurelia) {
const container = aurelia.container;
const httpClient = container.get(HttpClient);
const ea = container.get(EventAggregator);
httpClient.configure(config => {
config.withInterceptor({
request(request) {
ea.publish('http-request', request);
return request;
},
requestError(error) {
ea.publish('http-request-error', error);
throw error;
},
response(response) {
ea.publish('http-response', response);
return response;
},
responseError(error) {
ea.publish('http-response-error', error);
throw error;
}
});
});
aurelia.use
.standardConfiguration()
.developmentLogging()
.singleton(HttpClient, httpClient);
aurelia.start().then(() => aurelia.setRoot());
}
Because the header for my request must be set after the App has initialised - I can't do it in the configuration above like most tutorials online do.
Instead, it needs to be set as below;
import {inject} from "aurelia-framework";
import {HttpClient} from "aurelia-http-client";
import {EventAggregator} from "aurelia-event-aggregator";
#inject(HttpClient, EventAggregator)
export class Dashboard {
requestMethod = "GET";
constructor(HttpClient, EventAggregator) {
this.http = HttpClient;
this.ea = EventAggregator;
}
triggerGet() {
// HEADER NEEDS TO BE SET HERE USING THIS.FOO
this.http.get(this.url).then(response => {
console.log("GET Response", response);
});
}
}
I've tried variations of;
this.http.configure((configure) => {
if(this.username && this.password) {
configure.withDefaults({
headers: {
'Authorization': 'Basic ' + btoa(this.username + ":" + this.password)
}
});
}
})
But I can't get anything to alter the header where I need to, and maintain the configuration I've set up in main.js
Fabio Luiz in the comments set me on to a working solution. It's not ideal I don't think, but it works.
I've essentially created an AppState class that I use to pass the username/password to the interceptor;
export class AppState {
properties = {};
clear() {
this.properties = {};
}
set(key, value) {
this.properties[key] = value;
}
get(key) {
if(this.properties[key]) {
return this.properties[key];
} else {
return false;
}
}
}
It's pretty rough and ready, but it's only for a test Application so I'm happy with it.
Here's its use;
import {inject} from "aurelia-framework";
import {HttpClient} from "aurelia-http-client";
import {EventAggregator} from "aurelia-event-aggregator";
import {AppState} from "services/appState";
#inject(HttpClient, EventAggregator, AppState)
export class Dashboard {
constructor(HttpClient, EventAggregator, AppState) {
this.http = HttpClient;
this.ea = EventAggregator;
this.appState = AppState;
// Create listeners
}
run() {
if(this.username && this.password) {
this.appState.set('authenticate', true);
this.appState.set('username', this.username);
this.appState.set('password', this.password);
}
// Trigger HTTP Requests
}
}
Then my main.js file;
import {HttpClient} from 'aurelia-http-client';
import {EventAggregator} from 'aurelia-event-aggregator';
import {AppState} from 'services/appState';
export function configure(aurelia) {
const container = aurelia.container;
const httpClient = container.get(HttpClient);
const ea = container.get(EventAggregator);
const appState = container.get(AppState);
httpClient.configure(config => {
config.withInterceptor({
request(request) {
if(appState.get('authenticate')) {
let username = appState.get('username');
let password = appState.get('password');
request.headers.add("Authorization", "Basic " + btoa(username + ":" + password));
}
ea.publish('http-request', request);
return request;
},
requestError(error) {
ea.publish('http-request-error', error);
throw error;
},
response(response) {
ea.publish('http-response', response);
return response;
},
responseError(error) {
ea.publish('http-response-error', error);
throw error;
}
});
});
aurelia.use
.standardConfiguration()
.developmentLogging()
.singleton(HttpClient, httpClient);
aurelia.start().then(() => aurelia.setRoot());
}
Try creating an actual headers object like this:
this.http.configure((configure) => {
if(this.username && this.password) {
configure.withDefaults({
headers: new Headers({
'Authorization': 'Basic ' + btoa(this.username + ":" + this.password)
})
});
}
})