Im trying to create a plugin for manage the Oauth2 token data in my vuejs app.
I created the plugin following some few tutorials that are available on the internet.
var plugin = {}
plugin.install = function (Vue, options) {
var authStorage = {
getToken () {
let token = localStorage.getItem('access_token')
let expiration = localStorage.getItem('expiration')
if (!token || !expiration) {
return null
}
if (Date.now() > parseInt(expiration)) {
this.destroyToken()
return null
}
return token
},
setToken (accessToken, expiration, refreshToken) {
localStorage.setItem('access_token', accessToken)
localStorage.setItem('expiration', expiration + Date.now())
localStorage.setItem('refresh_token', refreshToken)
},
destroyToken () {
localStorage.removeItem('access_token')
localStorage.removeItem('expiration')
localStorage.removeItem('refresh_token')
},
isAuthenticated () {
if (this.getToken()) {
return true
} else {
return false
}
}
}
Vue.prototype.$authStorage = authStorage
}
export default plugin
but when a try to access the methods on the main.js file, i get error saying that the object is undefined.
import Vue from 'vue'
import App from './App'
import router from './router'
import AuthStorage from './AuthStorage.js'
Vue.config.productionTip = false
Vue.use(AuthStorage)
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requireAuth)) {
if (!Vue.$authStorage.getToken()) {
next({
path: '/',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next()
}
})
axios.defaults.headers.common = {
'Authorization': `Bearer ${Vue.$authStorage.getToken()}`
}
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
Inside the components the plugin works as expected. The problem is when o try to use in the main.js file.
I already tried with:
this.$authStorage
this.authStorage
Vue.authStorage
no success
You are adding $authStorage to the prototype of Vue.
Vue.prototype.$authStorage = authStorage
That means will only be available on instances of the Vue object (ie. the result of new Vue(...).
If you want $authStorage to be available as a property of Vue without creating an instance, you need to add it as a static property.
Vue.$authStorage = authStorage
But, if it were me, I would probably take a different approach. I would likely build the AuthStorage plugin like this:
const authStorage = {
getToken() {
let token = localStorage.getItem('access_token')
let expiration = localStorage.getItem('expiration')
if (!token || !expiration) {
return null
}
if (Date.now() > parseInt(expiration)) {
this.destroyToken()
return null
}
return token
},
setToken(accessToken, expiration, refreshToken) {
localStorage.setItem('access_token', accessToken)
localStorage.setItem('expiration', expiration + Date.now())
localStorage.setItem('refresh_token', refreshToken)
},
destroyToken() {
localStorage.removeItem('access_token')
localStorage.removeItem('expiration')
localStorage.removeItem('refresh_token')
},
isAuthenticated() {
if (this.getToken()) {
return true
} else {
return false
}
},
install(Vue) {
Vue.prototype.$authStorage = this
}
}
export default authStorage
Which would allow me to use it like this outside of Vue,
import Vue from 'vue'
import App from './App'
import router from './router'
import AuthStorage from './AuthStorage.js'
Vue.config.productionTip = false
Vue.use(AuthStorage)
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requireAuth)) {
if (!AuthStorage.getToken()) {
next({
path: '/',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next()
}
})
And reference it like this inside of Vue:
created(){
let token = this.$authStorage.getToken()
}
Here is an example.
Related
For now I have this piece of code
import addToast from '#/utils/toast-queue';
import routes from './routes';
import App from './app';
import './assets/styles/global.scss';
import { jsonRequest } from './utils/requests';
Vue.use(VueRouter);
const router = new VueRouter({ routes });
router.beforeEach(async (to, from, next) => {
const userInfo = await jsonRequest('GET', '/user/info');
const notAuthenticated = userInfo.status !== 200;
if (to.name !== 'login' && notAuthenticated) {
if (to.name === 'survey') {
next({ name: 'survey' });
} else {
addToast('Please login', { type: 'error' });
next({ name: 'login' });
}
} else {
next();
}
});
/* eslint-disable-next-line no-new */
new Vue({
el: '#app',
router,
render: h => h(App)
});
When a GET request comes from /user/info to the path /public/form I would like an user not authenticated to be redirected to the page displaying the form. How can I achieve this in vue.js?
I have declared the route in route.js file like this
{
path: '/public/form',
name: 'form',
component: () => import('./views/form'),
}
I updated the beforeEach() like this
router.beforeEach(async (to, from, next) => {
const userInfo = await jsonRequest('GET', '/user/info');
const notAuthenticated = userInfo.status !== 200;
if (to.name !== 'login' && notAuthenticated) {
if (to.name === 'form') {
next({ name: 'form' });
} else {
addToast('Please login', { type: 'error' });
next({ name: 'login' });
}
} else {
next();
}
});
but it seems not working
According to the documentation, you should use
return { name: 'Login' };
or
return '/login';
instead of
next({ name: 'login' });
The same for form.
I have been trying to set user data into Sentry's scope globally, so every time there's an error or event, user info is passed to it.
My app is built in Next.js, so naturally I added the config as it is in Sentry's documentation for Next.js.
I haven't got the idea on where to add the Sentry.setUser({id: user.Id}) method in order for it to set the user globally.
So far I have added it to the Sentry's _error.js file, inside the getInitialProps method:
import NextErrorComponent from 'next/error';
import * as Sentry from '#sentry/nextjs';
import { getUser } from '../lib/session';
const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => {
if (!hasGetInitialPropsRun && err) {
Sentry.captureException(err);
}
return <NextErrorComponent statusCode={statusCode} />;
};
MyError.getInitialProps = async (context) => {
const errorInitialProps = await NextErrorComponent.getInitialProps(context);
const { req, res, err, asPath } = context;
errorInitialProps.hasGetInitialPropsRun = true;
const user = await getUser(req, res);
// Set user information
if (user) {
console.log('Setting user');
Sentry.setUser({ id: user.Id });
}
else {
console.log('Removing user');
Sentry.configureScope(scope => scope.setUser(null));
}
if (res?.statusCode === 404) {
return errorInitialProps;
}
if (err) {
Sentry.captureException(err);
await Sentry.flush(2000);
return errorInitialProps;
}
Sentry.captureException(
new Error(`_error.js getInitialProps missing data at path: ${asPath}`),
);
await Sentry.flush(2000);
return errorInitialProps;
};
export default MyError;
But when trying to log errors, the user info doesn't show in Sentry, only the default user ip:
I have also tried setting the user after successful login, and still nothing..
Help is appreciated!!
Not sure if this is the right way, but the above solutions didn't work for me. So I tried calling setUser inside _app.tsx.
import { useEffect } from "react";
import { setUser } from "#sentry/nextjs";
import { UserProvider, useUser } from "#auth0/nextjs-auth0";
import type { AppProps } from "next/app";
function SentryUserManager() {
const { user } = useUser();
useEffect(() => {
if (user) {
setUser({
email: user.email ?? undefined,
username: user.name ?? undefined,
});
} else {
setUser(null);
}
}, [user]);
return null;
}
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider>
<Component {...pageProps} />
<SentryUserManager />
</UserProvider>
);
}
Still not sure why this worked for me and the other solutions didn't, but figured it was worth sharing.
I would suggest using the callback handler to set your Sentry user context.
import { handleAuth, handleLogin, handleCallback } from "#auth0/nextjs-auth0";
import * as Sentry from "#sentry/nextjs";
import { NextApiHandler } from "next";
const afterCallback = (_req, _res, session, _state) => {
Sentry.setUser({
id: session.user.sub,
email: session.user.email,
username: session.user.nickname,
name: session.user.name,
avatar: session.user.picture,
});
return session;
};
const handler: NextApiHandler = handleAuth({
async login(req, res) {
await handleLogin(req, res, {
returnTo: "/dashboard",
});
},
async callback(req, res) {
try {
await handleCallback(req, res, { afterCallback });
} catch (error) {
res.status(error.status || 500).end(error.message);
}
},
});
export default Sentry.withSentry(handler);
You can set the user in Sentry right after successful login
const handleLogin = {
try {
const res = await axios.post("/login", {"john#example.com", "password"})
if (res && res?.data) {
// Do other stuff
Sentry.setUser({ email: "john#example.com" });
}
}
}
Additionaly you can clear the user while logging out
const handleLogout = {
// Do othe stuff
Sentry.configureScope(scope => scope.setUser(null));
}
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',
],
I have a problem with my Vue application with the router on the authentication but only on the first page load...
All my routes are under a middleware to check if they are logged, so my router looke like this :
import Vue from 'vue'
import Router from 'vue-router'
import store from './store'
Vue.use(Router);
let router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'root',
component: resolve => require(['./views/Root.vue'], resolve),
meta: {
pageTitle: `Index`,
}
},
{
path: '/authentication/',
name: 'authentication',
component: resolve => require(['./views/Login.vue'], resolve),
meta: {
pageTitle: `Connexion`,
authNotRequired: true,
}
},
],
});
router.beforeEach((to, from, next) => {
//Si l'Utilisateur est authentifié :
if (!store.getters.isLoggedIn && !to.matched.some(r => r.meta.authNotRequired)) {
next({ name: 'authentication' });
}
else {
next();
}
});
export default router;
My session store is quite simple :
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersist from 'vuex-persist'
Vue.use(Vuex);
const vuexLocalStorage = new VuexPersist({
key: 'my.personal.key',
storage: window.localStorage,
})
export default new Vuex.Store({
plugins: [vuexLocalStorage.plugin],
state: {
session: {
sessionId: null,
},
},
getters: {
isLoggedIn: (state) => {
return (state.session.sessionId !== null);
},
},
mutations: {
save(state, session) {
state.session = session;
},
destroy(state) {
state.session = {};
},
},
actions: {
save(context, payload) {
return new Promise((resolve, reject) => {
context.commit('save', payload);
resolve();
});
},
destroy(context) {
return new Promise((resolve, reject) => {
context.commit('destroy');
resolve();
});
}
}
});
And so my Login function is quite simple too :
/* THE FORM WAS SUCCESSFULLY SENT TO THE API WHICH ANSWERED {obj.id: 456} */
onLogIn(obj) {
this.$store.dispatch('save', { sessionId: obj.id }).then(() => {
this.$router.push({ name: 'root' });
});
},
So, this code works fine when the application is already launched, if I disconnect and reconnect, the redirection works fine... Litterally everything is working great ...
BUT when the user first load the page and logIn, the router is not pushing the new route so the User stay on the authentication page, but the session is changed (if I put a console.log I have the sessionId in the store)
I don't really understan why the redirection not working on the first load, I found that when I do a localStorage.clear() then reload the page and try to connect it's also not working.
Do you have any idea why it's doing this?
I'm trying to create an App with Vue, and Vue-ressource
Actually i need to use ressource to made auth system by an API call.
But in my Auth.js (that i import into my login.vue) console said he can't read $http of undefined. So apparantly i can't reach 'this' (so vue).
Did i missed something ? Or its just a bad use ?
Thank you all
actually my main.js :
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
Vue.use(VueRouter)
Vue.use(VueResource)
import App from './components/App.vue'
import Login from './components/Login.vue'
import Home from './components/Containers.vue'
function requireAuth (to, from, next) {
if (!auth.loggedIn()) {
next({
path: '/',
query: { redirect: to.fullPath }
})
} else {
next()
}
}
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/home', name: 'home', component: Home, beforeEnter: requireAuth },
{ path: '/', component: Login },
{ path: '/logout',
beforeEnter (to, from, next) {
auth.logout()
next('/')
}}
]
})
new Vue({
el: '#app',
router,
render: h => h(App)
})
the login.vue
import auth from '../utils/auth'
export default {
data () {
return {
email: '',
pass: '',
error: false
}
},
methods: {
login () {
auth.login(this.email, this.pass, loggedIn => {
if (!loggedIn) {
this.error = true
} else {
console.log('good')
this.$router.replace('/home')
}
})
}
}
}
and my auth.js where the vue-ressource post is made :
export default {
login (email, pass, cb) {
cb = arguments[arguments.length - 1]
if (localStorage.token) {
if (cb) cb(true)
this.onChange(true)
return
}
pretendRequest(email, pass, (res) => {
if (res.authenticated) {
localStorage.token = res.token
if (cb) cb(true)
this.onChange(true)
} else {
if (cb) cb(false)
this.onChange(false)
}
})
},
getToken () {
return localStorage.token
},
logout (cb) {
delete localStorage.token
if (cb) cb()
this.onChange(false)
},
loggedIn () {
return !!localStorage.token
},
onChange () {}
}
function pretendRequest (email, pass, cb) {
setTimeout(() => {
this.$http.post('localhost:9000/api/login', {email: email, password: pass}).then(response => {
if (response.status === 200) {
cb({
authenticated: true,
token: Math.random().toString(36).substring(7)
})
} else {
cb({ authenticated: false })
}
}, response => {
console.log('error ' + response.status)
})
}, 0)
}
Replace vue-resource with axios. Easy to do. Vue-resource is not longer maintained by Vue team, so it's bad choice to use it.( https://medium.com/the-vue-point/retiring-vue-resource-871a82880af4#.dwmy5ymjx )
Axios is widely supported. https://github.com/mzabriskie/axios .
Nice laracasts about using axios with vue. You will quickly get it. https://laracasts.com/series/learn-vue-2-step-by-step/episodes/18
It is normal that you can't access Vue instance in your auth module. Try to learn more about using this and you will quickly get it 'why?'
To make ajax requests in your auth module, just import axios and use axios.post / axios.get
Any questions? Comment and I will explain more.