I want to write a middleware that checks the authentication and entitlement of the user. I get the authentication details from my store:
//store/index.js
const state = () => ({
auth: {
isLoggedIn: false
// and so on
}
});
and the entitlements from a plugin:
//plugins/entitlement.js
import axios from 'axios';
export default (context, inject) => {
const { env: { config: { entitlementUrl } }, store: { state: { auth: { access_token } } } } = context;
const headers = {
Authorization: `Bearer ${access_token}`,
'Content-Type': 'application/json'
};
inject('entitlement', {
isEntitled: (resourceId) => new Promise((resolve, reject) => {
axios.get(`${entitlementUrl}/entitlements`, { headers, params: { resourceId } })
.then(({ data }) => {
resolve(data.Count > 0);
})
.catch((error) => {
reject(error);
});
})
};
This is the middleware that I wrote but it doesn't work:
//middleware/isEntitled.js
export default function ({ app, store }) {
if(store.state.auth.isLoggedIn){
let isEntitled = app.$entitlement.isEntitled('someId');
console.log('entitled? ', isEntitled)
}
}
And then I add it to my config:
//nuxt.config.js
router: {
middleware: 'isEntitled'
},
I get the error isEntitled of undefined. All I want to do is to check on every page of application to see if the user is entitled! How can I achieve that?
If you look at the situation from the plugin side, you can do this:
First create a plugin:
export default ({app}) => {
// Every time the route changes (fired on initialization too)
app.router.beforeEach((to, from, next) => {
if(app.store.state.auth.isLoggedIn){
let isEntitled = app.$entitlement.isEntitled('someId');
console.log('entitled? ', isEntitled)
}
return next();
})
}
then add the plugin to your nuxt.config.js file:
plugins: [
'~/plugins/your-plugin.js',
],
Related
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 am trying to use jest with nextJS to test API. I am using a custom interceptor for all http request to have authorization token on header. Here is my interceptor code
Api.ts
import axios from 'axios';
import config from '../config/index';
const Api = () => {
const defaultOptions = {
baseURL: config.APIENDPOINT,
method: 'get',
headers: {
'Content-Type': 'application/json',
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the AUTH token for any request
instance.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
//#ts-ignore
config.headers.Authorization = token ? `${token}` : '';
return config;
});
instance.interceptors.response.use((res) => {
return res
});
return instance;
};
export default Api();
Here is the code to call the API
export const loadMerchants = async (id: any) => {
const data = await Api.get(config.APIENDPOINT + "/merchants/company/" + id)
console.log("data" ,data);
return (data)
}
And here is my test code
const axios = require('axios');
jest.mock('axios', () => {
return {
get: jest.fn(),
create: jest.fn(() => ({
interceptors: {
request: { use: jest.fn(() => Promise.resolve({ data: { foo: 'bar' } })) },
response: { use: jest.fn(() => Promise.resolve({ data: { foo: 'bar' } })) },
}
}))
}
})
it('Merchant API call', async () => {
axios.get.mockResolvedValue({
data: [
{
userId: 1,
id: 1,
title: 'My First Album'
},
{
userId: 1,
id: 2,
title: 'Album: The Sequel'
}
]
});
const merchants = await loadMerchants("1")
console.log(merchants) //always undefined
// expect(merchants).toEqual('some data');
});
on my API call if use axios.get instead of Api.get i get the correct results. I have looked into google and haven`t found any solutions.
Any help would be appreciated. Thank you.
I've created simple VueCLI auth module using axios and Vuex.
In store.js I've got all logic for tokens using api from session.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import sessionSerivce from '#/services/session.js'
Vue.use(Vuex)
Vue.use(require('vue-cookies'))
export const store = new Vuex.Store({
state: {
status: '',
accessToken: $cookies.get('accessToken') || '',
refreshToken: $cookies.get('refreshToken') || '',
user: $cookies.get('user') || '',
},
actions: {
login({ commit }, data) {
return new Promise((resolve, reject) => {
commit('auth_request')
sessionSerivce
.logIn(data)
.then((resp) => {
const commitData = {
accessToken: resp.data.access_token,
refreshToken: resp.data.refresh_token,
user: resp.data.user,
}
$cookies.set('accessToken', commitData.accessToken)
$cookies.set('refreshToken', commitData.refreshToken)
$cookies.set('user', JSON.stringify(commitData.user))
axios.defaults.headers.common['Authorization'] =
commitData.accessToken
commit('auth_success', commitData)
resolve(resp)
})
.catch((err) => {
commit('auth_error')
$cookies.remove('accessToken')
$cookies.remove('refreshToken')
$cookies.remove('user')
reject(err)
})
})
},
verifyToken({ commit, state }) {},
register({ commit }, data) {
return new Promise((resolve, reject) => {
commit('auth_request')
sessionSerivce
.register(data)
.then((resp) => {
const commitData = {
accessToken: resp.data.access_token,
refreshToken: resp.data.refresh_token,
user: resp.data.user,
}
$cookies.set('accessToken', commitData.accessToken)
$cookies.set('refreshToken', commitData.refreshToken)
$cookies.set('user', JSON.stringify(commitData.user))
axios.defaults.headers.common['Authorization'] =
commitData.accessToken
commit('auth_success', commitData)
resolve(resp)
})
.catch((err) => {
commit('auth_error')
$cookies.remove('accessToken')
$cookies.remove('refreshToken')
$cookies.remove('user')
reject(err)
})
})
},
logout({ commit }) {
return new Promise((resolve, reject) => {
commit('logout')
$cookies.remove('accessToken')
$cookies.remove('refreshToken')
$cookies.remove('user')
delete axios.defaults.headers.common['Authorization']
resolve()
})
},
},
mutations: {
auth_request(state) {
state.status = 'loading'
},
auth_success(state, commitData) {
state.status = 'success'
state.accessToken = commitData.accessToken
state.refreshToken = commitData.refreshToken
state.user = commitData.user
},
auth_error(state) {
state.status = 'error'
},
refresh_token(state, accessToken) {
state.accessToken = accessToken
},
logout(state) {
state.status = ''
state.accessToken = ''
state.refreshToken = ''
state.user = ''
},
},
getters: {
isLoggedIn: (state) => {
return !!state.accessToken
},
authStatus: (state) => state.status,
},
})
In main.js I use this function to check:
router.beforeEach(async (to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.getters.isLoggedIn) {
next()
return
}
next('/login')
} else next()
})
The problem is that code above checks only if access token exists in Vuex. I want to verify using api before any route, that requires auth and if it's not successfully I want to refresh It with api using refresh token. If both are unsuccessful(access and refresh tokens are both invalid) user gonna log out.
Example route which requires auth:
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
},
I've tried code like this:
router.beforeEach(async (to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.state.accessToken) {
await store.dispatch('verifyToken')
if (store.getters.isLoggedIn) {
next()
return
}
}
next('/login')
} else next()
})
Action in Vuex:
verifyToken({ commit, state }) {
const accessToken = state.accessToken
const refreshToken = state.accessToken
sessionSerivce
.verifyToken(accessToken)
.then((resp) => {})
.catch((err) => {
sessionSerivce
.refreshToken(refreshToken)
.then((resp) => {
console.log('Refreshuje token')
const accessToken = resp.data.access_token
localStorage.setItem('accessToken', accessToken)
axios.defaults.headers.common['Authorization'] = accessToken
commit('refresh_token', accessToken)
})
.catch((err) => {
commit('logout')
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
delete axios.defaults.headers.common['Authorization']
})
})
},
Note that in code above i used localstorage but i've changed my mind and I'm using cookie, as You can see in previous code.
Unfortunately this code didn't work as expected - if (store.getters.isLoggedIn) { next(); return; } is starting to execute before await store.dispatch('verifyToken') ends, which is bad.
Any ideas?
I am writing code to call api using axios. So, for this code I have to send an otp to the api along with an authorization token. I am using vuex store.
I am getting an error of 406(not applicable). This is the code I have written.
import { isAuthenticated } from './auth'
import axios from 'axios'
export default ({
state: {
},
mutations: {
},
getters: {
},
actions: {
VERIFY: (payload) => {
const userId = isAuthenticated().user._id
return axios
.post(apilink, payload, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${isAuthenticated().token}`,
Accept: 'application/json'
}
}).then(response => {
console.log(response)
return response.data
})
.catch(error => {
if (error) {
console.log(error)
}
})
}
},
modules: {
}
})
<template>
<mdb-btn color="info" #click="verify()">Verify</mdb-btn>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js">
data () {
return {
value: ''
}
},
methods: {
verify () {
this.$store.dispatch('VERIFY', {
otp: this.value
}).then(success => {
console.log(success)
}).catch(error => {
console.log(error)
})
}
}
</script>
I think it's the problem with authorization part. Please help me.
isAuthenticated is funtion used to get data from localStorage
export const isAuthenticated = () => {
if (localStorage.getItem('auth')) {
return JSON.parse(localStorage.getItem('auth'))
}
return false
}
406 error is appearing because of Accept parameter in the header try after removing "Accept: 'application/json'"
I came to an point where i have a bunch of endpoints that behave the same like:
http:://api.development/projects/status/types
http:://api.development/projects/errors/types
http:://api.development/projects/priority/types
They all have the same verbs: GET, POST (add), PUT (edit) and DELETE and they share the same data structure:
{
name: "",
description: ""
}
Therefore the view and the logic to manage that on my client will be the same.
I am using VUE for the client. I thought of creating a component to reuse the view and create three other components that includes that component. Therefore the logic will be written in all of this three parent components.
But how can I implement the logic to reuse it across them? The only piece of code will change will be the endpoints.
You can create a service file, and there you declare your API calls, and then just export that file as a component and reuse the calls anywhere in your app.
It would look something like this:
import axios from 'axios'
const api = 'API'
export default {
data() {
return {
user: `${api}/some/route`,
hotels: `${api}/other/route/`
}
},
methods: {
getHeaders() {
return {
headers: {
Authorization: 'Bearer ' + 'TOKEN',
'Content-Type': 'application/json'
}
}
},
getModule(route, cb) {
axios
.get(route, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(err => {
cb(err)
})
},
postModule(route, data, cb) {
axios
.post(route, data, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
cb(e)
})
},
putModule(route, data, cb) {
axios
.put(route, data, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
console.log(e)
})
},
deleteModule(route, id, cb) {
axios
.delete(route + id, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
console.log(e)
})
}
}
}
In the component where you want to execute the call you do this:
import ServiceFileName from '#/services/YourServiceFileName'
methods:{
getData () {
Main.methods.getModule(Main.data().hotels, data => {
console.log(data)
})
}
}
You mean something like mixins?