Authorization header doesn't work with http GET requests - javascript

I'm using VueSession in my project. I created a login component and I'm passing data to my backend (Django, returns JWT token). Here is my problem. My login works fine, it returns JWT but when I want to get data from other endpoints I'm getting error 401 (Authentication credentials were not provided). When I'm using curl in my terminal everything works fine.
curl -X POST -d "username=test&password=test" http://localhost:8000/api/token/auth/
it returns token
curl -H "Authorization: JWT <my_token>" http://localhost:8000/protected-url/
and it returns data from website
Here is what I set up in my Vue project.
Login.vue
<script>
import Vue from 'vue'
export default {
name: 'Login',
data () {
return {
username: '',
password: ''
}
},
methods: {
login: function (username, password) {
let user_obj = {
"username": username,
"password": password
}
this.$http.post('http://192.168.1.151:8000/api/token/auth', user_obj)
.then((response) => {
console.log(response.data)
this.$session.start()
this.$session.set('jwt', response.data.token)
Vue.http.headers.common['Authorization'] = 'JWT' + response.data.token
// this.$router.push('/')
})
.catch((error_data) => {
console.log(error_data)
})
}
}
}
</script>
HereIWantUserGETRequest.vue
<script>
export default {
data() {
return {
msg: "Welcome",
my_list: []
}
},
beforeCreate() {
// IF SESSION DOESN'T EXIST
if (!this.$session.exists()) {
this.$router.push('/account/login')
}
},
mounted() {
this.getData()
},
methods: {
getData: function() {
this.$http.get('http://192.168.1.151:8000/api/user/data')
.then((response) => {
console.log(response.data)
this.my_list = response.data
})
.catch((error_data) => {
console.log(error_data)
})
}
}
}
</script>
And of course I set up VueSession and VueResource in main.js
import VueSession from 'vue-session'
import VueResource from 'vue-resource'
Vue.use(VueResource)
Vue.use(VueSession)

Edit
Vue.http.headers.common['Authorization'] = 'JWT' + response.data.token
with
Vue.http.headers.common['Authorization'] = 'JWT ' + response.data.token
Hope it will help you

You don't actually store your jwt token anywhere in your browser (with a cookie or a localStorage). Therefore Vue has the token in memory only for the runtime of that page (in the sense of a one page app) you've request your jwt token in. According to the github docs of VueSession the option to store the token in your browser is false by default. Just set it to true like this:
#main.js
var options = {
persist: true
}
Vue.use(VueSession, options)
I personally don't use this library. I usually do it from scratch using axios, Vuex and localStorage. It's really not that difficult, the pattern is described pretty well here.

The problem was with this line Vue.http.headers.common['Authorization'] = 'JWT ' + response.data.token. To fix it I needed to at this to my main.js file:
if (this.$session.exists()) {
var token = this.$session.get('jwt')
console.log(token)
Vue.http.headers.common['Authorization'] = 'JWT ' + token
}
And now it's working.

Related

Dev Server with vite + vue3, route 404 not found

Im in the process of moving an app from Vue 2 -> 3
I decided to take a moment to really upgrade and refactor all my repo and that led to using Vue3 recs on new tech, one being vite
My problem is I don't totally understand how the backend API process works so im struggling to move my api route from vue-cli to vite.
I would like to keep using the logic that call functions from api/users to remain in place but im open to a better option
Ultimately I get 404 - Not Found as my response which means its cant find the route
Heres my api/user.js
import request from '../utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
utils/Request.js
import axios from 'axios'
import { ElMessageBox, ElMessage } from 'element-plus'
import { userStore } from '../stores/user'
import { getToken } from '../utils/auth'
// create an axios instance
const service = axios.create({
baseURL: import.meta.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
const useStore = userStore;
console.log("Req", "Req Init");
if (useStore.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
console.log("failed") // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
console.log("Res", "Res Init");
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
ElMessage({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
ElMessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
And a peek at my store that actually calls the endpoint
import { login, logout, getInfo } from '../api/user'
actions: { // user login
login({ commit }, userInfo) {
const { username, password } = userInfo
// **Call is made here to 'login'**
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
Lastly where the call originates from: this.store.login in my login.vue component
this.store.login('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
This is my first StackOverflow post, so be kind if I need to include something else and thanks to any ideas or tips. Thank you
I've tried to search on different ports but the route still comes back as undefined.
I tried to change the vite config to include the server option but it still doesn't seem to help.
Not sure If I need to modify the config or not but I did have some settings related to the server mock on my old webpack config

React Native access token expiration/renewal upon 403 response code from a RTK Query API

I am calling an API defined using RTK Query, within a React Native + Redux Toolkit + Expo app. This is secured with an authentication / authorization system in place i.e. access token (short expiration) and refresh token (longer expiration).
I would like to avoid checking any access token expiration claim (I've seen people suggesting to use a Redux middleware). Rather, if possible, I'd like to trigger the access token renewal when the API being requested returns a 403 response code, i.e. when the access token is expired.
This is the code calling the API:
const SearchResults = () => {
// get the SearchForm fields and pass them as the request body
const { fields, updateField } = useUpdateFields();
// query the RTKQ service
const { data, isLoading, isSuccess, isError, error } =
useGetDataQuery(fields);
return ( ... )
the RTK Query API is defined as follows:
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import * as SecureStore from "expo-secure-store";
import { baseUrl } from "~/env";
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({
baseUrl: baseUrl,
prepareHeaders: async (headers, { getState }) => {
// retrieve the access_token from the Expo SecureStore
const access_token = await SecureStore.getItemAsync("access_token");
if (access_token) {
headers.set("Authorization", `Bearer ${access_token}`);
headers.set("Content-Type", "application/json");
}
return headers;
},
}),
endpoints: (builder) => ({
getData: builder.query({
// body holds the fields passed during the call
query: (body) => {
return {
url: "/data",
method: "POST",
body: body,
};
},
}),
}),
});
export const { useGetDataQuery } = api;
I understand that when the API returns isError = true and error = something 403 I need to renew the access token within the Expo SecureStore (and there's a function already in place for that). However I have no idea about how can I query the RTKQ API again, on the fly, when it returns a 403 response code, and virtually going unnoticed by the user.
Can someone please point me in the right direction?
I got the hang of it, massive thanks to #phry! I don't know how I could have missed this example from RTKQ docs but I'm a n00b for a reason after all.
This being said, here's how to refactor the RTKQ api to renew the access token on the fly, in case some other react native beginner ever has this problem. Hopefully this is a reasonable way of doing this
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import * as SecureStore from "expo-secure-store";
import { baseUrl } from "~/env";
import { renewAccessToken } from "~/utils/auth";
// fetchBaseQuery logic is unchanged, moved out of createApi for readability
const baseQuery = fetchBaseQuery({
baseUrl: baseUrl,
prepareHeaders: async (headers, { getState }) => {
// retrieve the access_token from the Expo SecureStore
const access_token = await SecureStore.getItemAsync("access_token");
if (access_token) {
headers.set("Authorization", `Bearer ${access_token}`);
headers.set("Content-Type", "application/json");
}
return headers;
},
});
const baseQueryWithReauth = async (args, api) => {
let result = await baseQuery(args, api);
if (result.error) {
/* try to get a new token if the main query fails: renewAccessToken replaces
the access token in the SecureStore and returns a response code */
const refreshResult = await renewAccessToken();
if (refreshResult === 200) {
// then, retry the initial query on the fly
result = await baseQuery(args, api);
}
}
return result;
};
export const apiToQuery = createApi({
reducerPath: "apiToQuery",
baseQuery: baseQueryWithReauth,
endpoints: (builder) => ({
getData: builder.query({
// body holds the fields passed during the call
query: (body) => {
return {
url: "/data",
method: "POST",
body: body,
};
},
}),
}),
});
export const { useGetDataQuery } = apiToQuery;

Different headers used in Axios patch

I spent an hour looking in the Chrome console and I cannot see where this bug comes from.
I am finishing an update of OAuth implementation in my Vue app.
The story begins when socialLink.js finds out that a new user must be created. Vue component Vue-authentication depends on the presence of access_token in a response so I return some dummy text:
return api.sendResponse(res, { email, name, socialId, access_token: 'abcd' });
The library stores this value in localStorage:
After a redirect, the SignUp.vue is rendered and I complete the form. The first communication with the server is a Vuex call to create a new user:
response = await this.$store.dispatch('CREATE_USER_PROFILE', payload);
Which returns a real short lived JWT token:
const token = auth.createToken(userId, nickname, new Date(), null, false, '1m');
return api.sendCreated(res, api.createResponse(token));
Which I store in the Vue page afterwards:
const { data } = response;
const token = data.data;
if (token === undefined) {
this.error = this.$t('sign-up.something-went-wrong');
return false;
}
I checked that the token contains what the server returned:
Request URL: https://beta.mezinamiridici.cz/api/v1/users
Request Method: POST
Status Code: 201 Created
{"success":true,"data":"eyJhbGciOiJIUzI1NiIs...Tl8JFw2HZ3VMXJk"}
Then I call another Vuex method and pass the current JWT token:
await this.$store.dispatch('UPDATE_USER_PROFILE', {
I checked in the Vuex devtools that there really is the correct JWT token. I then pass it further to api.js.
Here I create an Axios configuration holding an Authorization header:
function getAuthHeader(context, jwt = undefined, upload) {
const config = { headers: { } };
if (jwt || (context && context.rootState.users.userToken)) {
config.headers.Authorization = `bearer ${jwt || context.rootState.users.userToken}`;
}
Again, I checked that the correct JWT token is used there.
Finally, I pass all data to Axios:
function patch(endpoint, url, body, context, jwt) {
const headers = getAuthHeader(context, jwt);
console.log(headers);
if (endpoint === 'BFF') {
return axios.patch(`${VUE_APP_BFF_ENDPOINT}${url}`, body, headers);
} else {
return axios.patch(`${VUE_APP_API_ENDPOINT}${url}`, body, headers);
}
}
Which I log and can confirm the correct JWT is still there:
bearer eyJhbGciOiJIUzI1N....8JFw2HZ3VMXJk
There is nothing that could change the header now to abcd, but, the 'Network' tab shows it:
And the server fails with a parse error.
Has anybody got an idea why Axios uses the Authorization header with a different value than I pass it?
Ok, mystery solved. vue-authenticate is the reason, because, it creates Axios interceptors and handles the Authorization header itself.
vue-authenticate.common.js:
var defaultOptions = {
bindRequestInterceptor: function ($auth) {
var tokenHeader = $auth.options.tokenHeader;
$auth.$http.interceptors.request.use(function (config) {
if ($auth.isAuthenticated()) {
config.headers[tokenHeader] = [
$auth.options.tokenType, $auth.getToken()
].join(' ');
} else {
delete config.headers[tokenHeader];
}
return config
});
},
My code is more complex and it supports internal accounts with email/password so this code is breaking mine. The interceptor must be present and be a function, so the solution was:
Vue.use(VueAuthenticate, {
tokenName: 'jwt',
baseUrl: process.env.VUE_APP_API_ENDPOINT,
storageType: 'localStorage',
bindRequestInterceptor() {},
bindResponseInterceptor() {},
providers: {
facebook: {
clientId: process.env.VUE_APP_FACEBOOK_CLIENT_ID,
redirectUri: process.env.VUE_APP_FACEBOOK_REDIRECT_URI,
},

How to access cookie in nuxt fetch

I am looking for way to access cookie in my nuxt fetch. Here is my code;
async fetch() {
const { store, route } = this.$nuxt.context
const { data } = await axios.get(
`${process.env.baseUrl}/user-saved-homes/?ordering=${route.query.ordering}`,
{
headers: {
Authorization: 'Token ' + my_token,
},
}
)
store.commit('ADD_SAVED_HOMES', data.results)
},
I need fetch because I was access to $fetchState.pending. But I have tried looking for a way to access cookie in the nuxt fetch, but I haven't found any. Please, I need help here
Since fetch by default runs on the server, I can't use js-cookies(javascript library), to get the token. So the best way to make this happen is for nuxt fetch to run on the client side.
So adding fetchOnServer: false enabled me to use js-cookies to get the token
so my code is now;
<script>
import axios from 'axios'
import Cookies from 'js-cookies'
export default {
async fetch() {
const { store, route } = this.$nuxt.context
const { data } = await axios.get(
`${process.env.baseUrl}/user-saved-homes/`,
{
headers: {
Authorization: 'Token ' + Cookies.getItem('token'),
},
}
)
store.commit('ADD_SAVED_HOMES', data.results)
},
fetchOnServer: false,
}

axios header Authorization using vue.js

I am trying to use a header token with axios. However I am presented with a CORS error as I am clearly not passing over the token correctly (moving to a not authorized feed works)
Here is my http-common.js file
const token = `08E1B4C220E671AC6A48`
// my user app token from micro.blog 08E1B4C220E671AC6A48
export const HTTP = axios.create({
// baseURL: 'https://micro.blog/feeds/adamprocter.json'
baseURL: 'https://micro.blog',
headers: {
Authorization: `Bearer ${token}`
}
})
and here is my Timeline.vue component
import { HTTP } from '#/http-common'
export default {
components: {
MicroPosts
},
data() {
return {
posts: []
}
},
created() {
// no auth get = HTTP.get('')
HTTP.get('/account/verify')
.then(response => {
//console.log(response.data)
this.posts = response.data.items
})
.catch(error => {
console.log('caught error' + error.response)
})
}
}
The URL is correct but the token is failing (I believe)
POST /account/verify — Accepts an app token (which I have set up) and returns an auth token and other details.
This is the API documentation which is a little sparse but
http://help.micro.blog/2017/api-json/
http://help.micro.blog/2018/api-authentication/
I am sure it is something obvious, any help much appreciated.
The documentation says /account/verify accepts POST. You are sending a GET.

Categories

Resources