Nuxt auth user reset after browser refresh - javascript

i'm building an app with user login and register info. I use nuxt/auth module for handling the authentification. Whenever the user get logs in the state changes to true without a problem. The only issue i'm having is when i call set user method , the user info get registered successfully, but whenever i refresh the browser i lose the user data even though in the first place it was set successfully.
my nuxt.js config for nuxt auth module
auth: {
strategies: {
local: {
token: {
property: "token",
global: true,
},
redirect: {
"login": "/account/login",
"logout": "/",
"home": "/page/ajouter-produit",
"callback": false
},
endpoints: {
login: { url: "http://localhost:5000/login", method: "post" },
logout: false, // we don't have an endpoint for our logout in our API and we just remove the token from localstorage
user:false
}
}
}
},
My register/login component
async typeForm(e) {
this.typesubmit = true;
// stop here if form is invalid
this.$v.$touch();
if (this.$v.typeform.$anyError) {
return;
}
const user = await axios.post(`${baseUrl}register`,{
username:this.typeform.username,
password:this.typeform.password,
email:this.typeform.email,
tel:this.typeform.tel,
adresse:this.typeform.adresse,
store:this.typeform.store,
})
.then(response => {
console.log(response.data)
return response.data.user;
}).catch( (error) => {
this.error = ''
this.error = error.response.data
})
if(user){
let loginData = {email:this.typeform.email, password:this.typeform.password}
const response = await this.$auth.loginWith('local', { data: loginData})
.then(response => {
this.$auth.setUser(response.data.user) // the user is set without a problem, everything works fine.
return response.data;
}).catch( (error) => {
this.errors = error.response.data
console.log(error.response)
})
}
}
When i console log the state and the user in the first place everything works fine
console.log(this.$store.state.auth.user) // logs all user data i get from the api call. but when i refresh i get an empty object
console.log(this.$store.state.auth.loggedIn) // logs true , and even after i refresh it's still true
please help

Problem solved. what i did is to add to my login function
this.$auth.$storage.setUniversal('user', response.data.user, true)
works like a charm.

I have the samie issue.
After added this.$auth.$storage.setUniversal('user', response.data.user, true), the user is logged out after refreshing the page.
Here is my code :
this.$auth
.loginWith("local", {
data: {
email: this.connexionLogin,
password: this.connexionPassword
}
})
.then( (response) => {
this.$auth.setUser(response.data.user);
this.$auth.$storage.setUniversal('user', response.data.user, true)
this.$emit("connexionOk");
and my nuxt.config :
auth: {
watchLoggedIn: true,
resetOnError: true,
redirect: {
login: "/admin/login",
logout: "/admin/login",
callback: "/callback",
home: '/admin/', // Pour ne pas ĂȘtre redirigĂ© vers la home suite authentification
},
strategies: {
local: {
token: {
property: "tokens.access.token",
},
user: {
property: 'user',
autoFetch: false
},
endpoints: {
login: {
url: "v1/auth/login",
method: "post",
},
logout: false,
},
},
},
},

Related

failed login redirects me to /api/auth/error on next-auth

I'm using next-auth v. 4.18.8 in my login page. This is the final project of my Fullstack JS course. I'm using a newer version than the one used in the course (next-auth v. 3 is used)
When I insert the correct password, everything works as it should (it redirects me to the desired page).
Inserting the wrong password should throw me to /auth/signin?i=1 so I can handle this query.
However, it redirects me to http://127.0.0.1:3000/api/auth/error?error=Request%20failed%20with%20status%20code%20401
On console, it shows "POST http://localhost:3000/api/auth/callback/credentials? 401 (Unauthorized)"
Here's my code:
Frontend: Login Page
const handleFormSubmit = async values => {
signIn('credentials', {
email: values.email,
password: values.password,
callbackUrl: 'http://127.0.0.1:3000/user/dashboard'
})
}
Frontend: [...nextauth].js
export const authOptions = {
providers: [
CredentialsProvider({
name: 'credentials',
async authorize(credentials, req) {
const res = await axios.post('http://127.0.0.1:3000/api/auth/signin', credentials)
const user = res.data
if (user) {
return user
} else {
throw '/auth/signin?i=1'
}
}
})
],
session: {
jwt: true
},
jwt: {
secret: process.env.JWT_TOKEN
},
adapter: MongooseAdapter(process.env.MONGODB_URI)
}
export default NextAuth(authOptions)
Backend: signin.js controller
const authSignin = {
post: async (req, res) => {
const {
name,
email,
password,
} = req.body
await dbConnect()
const user = await UsersModel.findOne({ email })
if (!user) {
return res.status(401).json({ success: false, message: "invalid" })
}
const passIsCorrect = await compare(password, user.password)
if (passIsCorrect) {
return res.status(200).json({
_id: user._id,
name: user.name,
email: user.email
})
}
return res.status(401).json({ success: false, message: "invalid" })
}
}
export { authSignin }
Finally:
Backend: signin.js routes (using Next Connect):
import nextConnect from 'next-connect'
import { authSignin } from '../../../src/controllers/auth/signin'
const route = nextConnect()
route.post(authSignin.post)
export default route
One thing I noticed is that when inserting a wrong password, when the code reaches this line on controller:
return res.status(401).json({ success: false, message: "invalid" })
It wont continue to execute the [...nextauth].js file after axios.post, therefore not executing the code below, which should give me the 'i' query to handle on frontend (as stated in next-auth documentation):
if (user) {
return user
} else {
throw '/auth/signin?i=1'
}
The repository is on GitHub
I think if you pass redirect:false here
const handleFormSubmit = async values => {
signIn('credentials', {
email: values.email,
password: values.password,
callbackUrl: 'http://127.0.0.1:3000/user/dashboard',
redirect: false,
})
}
Looks like when next-auth encounters an error, it automatically redirects. By setting the redirect option it will not automatically redirect so you could handle the error on client side like this
const handleFormSubmit = async values => {
const signInResult=signIn('credentials', {
email: values.email,
password: values.password,
callbackUrl: 'http://127.0.0.1:3000/user/dashboard',
redirect: false,
})
If (signInResult.error){
// Handle Error on client side
}
}
Also you should not make api request in authorize. it will delay the process. you could run the signin logic inside the authorize

Problem using Next-Auth with Credentials Provider for authenticating on existing system

I am using Next-Auth Credentials provider to authenticate using our existing API.
When I follow the directions on https://next-auth.js.org/configuration/callbacks
like this:
callbacks: {
async jwt({ token, user }) {
if (user) {
token.accessToken = user.jwt
}
return token
},
async session({ session, token, user }) {
session.accessToken = token.accessToken
return session
}
}
the resulting session object from useSession() looks like this:
{
expires: "2022-03-22T18:29:02.799Z",
user: {email: 'john#nextIsGreat.com'}
}
I can't use that as it does not have the token available.
So I was able to make up my own working solution, but it is kind of strange because of the way things are grouped together. Here is what I am doing now, that I am trying to figure out how to do better. I use comments to point out the problem areas:
[...nextauth].js:
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import axios from 'axios'
export default NextAuth({
providers: [
Credentials({
name: 'Email and Password',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' }
},
authorize: async (credentials) => {
const url = process.env.API_URL + '/authenticate'
const result = await axios.post(url, {
username: credentials.username,
password: credentials.password
})
const user = result.data
console.log(user)
//It logs this:
/*
{
jwt: 'eyJhbasU1OTJ9.NQ356H4Odya62KmN...', //<---***This is the token i pass in to all of my API calls****
user: {
userId: 207,
email: 'john#nextIsGreat.com',
firstName: 'John',
lastName: 'Doe',
roleId: 1,
}
}
*/
if (user) {
return Promise.resolve(user)
} else {
return Promise.resolve(null)
}
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
if (user.jwt) {
token = { accessToken: user.jwt, restOfUser: user.user }
}
}
return token
},
async session(seshProps) {
return seshProps
}
}
})
Home.js:
export const Home = () => {
const { data: session } = useSession()
console.log(session)
//LOGS THIS --->
/*
{
"session": { "user":{}, "expires":"2022-03-22T17:06:26.937Z"},
"token":{
"accessToken":"eyJ...",
"iat":1645376785,
"exp":1647968785,
"jti":"41636a35-7b9a-42fd-8ded-d3dfgh123455a"
"restOfUser": {
"userId":207,
"email":"john#nextIsGreat.com",
"firstName":"John",
"lastName":"Doe",
"roleId":1
}
}
{
*/
const getPosts=()=> {
const url = 'localhost:4000/posts'
const {data} = axios.get(url, {
Authorization: session.token.accessToken <--**This is the way I am calling my API
})
console.log(data)
}
return (
<div onClick={getPosts}>
Hello, {session.token.restOfUser.firstName}
/* I have to access it like this now, which seems wrong ***** */
</div>
)
}
Cheers for creating your own solution but you do not need it. NextAuth CredentialsProvider handles it already by setting your NextAuth session configuration to session: {strategy: "jwt", ... }.
You can also remove your callbacks for jwt() and session() and remove your owned generated JWT access token. As you do not need it, this way you can authenticate your existing system.
And at your CredentialsProvider({authorize(){}} authorize method. If you had directly connected to the user database, you can directly look up the user credential without doing a post request since it is already considered a server-side function.

URIError in NuxtJS production when hosting on vercel

I am developing an application with ssr in nuxt. The problem I have is when I run "npm start" after doing the build and generate. The application starts working normally but when I try to log in it doesn't work although in development mode it works perfectly. The api is built with express and I am using tokens and nuxt auth as authentication method. The server endpoints declared in the auth strategy never get executed, use console.log () on the login endpoint handler to check. Anyone have any idea how I can solve this problem? Thanks for your time!
Login component script:
<script>
import { mapGetters } from 'vuex'
import index from './index.vue'
export default {
components: {
index,
},
data() {
return {
email: '',
password: '',
}
},
computed: {
...mapGetters(['isAuthenticated']),
},
methods: {
close() {
this.$router.push('/')
},
login() {
const button = document.querySelector('.center-form button')
button.disabled = true
button.innerHTML = '...'
const data = { password: this.password, email: this.email }
this.$auth
.loginWith('local', { data })
.then((x) => {
this.$auth.strategy.token.set(x.data.token)
this.$router.push('publicar-inmueble')
})
.catch((err) => {
console.log(err)
})
},
},
}
</script>
Nuxt auth strategy:
auth: {
strategies: {
local: {
token: {
property: 'token'
},
user: {
property: 'user'
},
endpoints: {
login: { url: '/server/api/usuarios/login', method: 'post', propertyName: 'data' },
user: { url: '/server/api/usuarios/mi-perfil', method: 'get', propertyName: 'data' },
logout: { url: '/server/api/usuarios/logout', method: 'delete' }
}
}
}
},
Store:
export const getters = {
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.auth.user
}
}
Go to your app's settings on vercel, the URL should look like this:
https://vercel.com/<your-username>/<your-project>/settings/environment-variables
There, drop in your env variable (my screenshot is a value example!) and trigger a build of your app. Should work fine then.

Vue router and vuex issue with redirecting based on a value

I have a Vue application that I am trying to have it that when the user has not paid and trial is over they are redirected to /billing and displayed an error message. I also would like it so that if they have paid or are still in their trial period that they can use the application.
storeUser code in my store.js
storeUser(state, user) {
state.user = user
state.isPaid = user.isPaid
state.isTrial = user.isTrial
},
data passed into storeUser as 'user'
{
name: "Joe",
isPaid: false,
isTrial: false
}
Data showing in my vuex store using the chrome vui extention
{
name: "Joe",
isPaid: null,
isTrial: null
}
Not sure why the data is being input wrong since I can console.log the correct data in the storeUser function. However if I look into the user portion I can see it as the correct false values. When I try to specify this in the code below for the vue router it says that is can't read it because it's null. I assume this is just an async issue.
state in store.js
state: {
token: null,
isPaid: null,
isTrial: null,
error: {
registerErrorMessage: '',
loginErrorMessage: '',
resetError: ''
},
user: null
}
main.js which contains my vue router
} else if(to.path != '/billing' && !(store.state.isPaid && store.state.isTrial)) {
next({
path: '/billings',
query: {
paid: false,
}
})
Can anyone spot a potential issue or maybe a solution to my issue? This code should be enough to reproduce the issue though if missing I can provide more, there is no public repo to show the rest of the code in.
EDIT**
So something weird happened.. I am now seeing more correct data than before (isPaid and isTrial are valid) however I'm still not able to go to other routes now.
Adding output of store.state at the beginning of my beforeEach
{
token: 'random string',
isPaid: true,
isTrial: false,
error: {},
user: {
name: "Joe",
isPaid: true,
isTrial: false
}
}
EDIT 2**
storeUser({commit, state}) {
if(!state.token) return
axios.get('/user/userInfo')
.then(res => {
if(res.data.success) {
commit('storeUser', {
name: res.data.user.name,
isPaid: res.data.user.company.stripe.isPaid,
isTrial: res.data.user.company.stripe.isTrial
})
}
})
.catch(err => {
console.log(err)
})
},
EDIT 3**
Here is my whole vue route from main.js
router.beforeEach((to, from, next) => {
store.dispatch('tryAutoLogin')
console.log(store.state) // this is test only not for prod
if(!store.state.token && (to.path == '/login'
|| to.path == '/signup'
|| to.path == '/forgot'
|| to.path == '/resend'
|| to.path.includes('/confirmation/')))
{
return next()
} else if (to.path == '/signup') {
return next({ query: {plan: from.query.plan }})
} else if(to.path != '/billing' && !(store.state.isPaid && store.state.isTrial)) {
next({
path: '/billing',
query: {
paid: false,
}
})
} else if(store.state.token) {
return next()
} else {
return next('/login')
}
})
You can see I do the auto login which just checks if a token exists or not that's it. It's not related to the issue.
EDIT 4**
I have an idea but not sure how to implement it.. Use promises to make sure the data is right. My confusion on the promise part is getting them to work together. So I'm think authUser mutation then somehow make the storeUser action a promise that I can resolve in my beforeEach
Actions
tryAutoLogin({commit, dispatch}) {
const token = localStorage.getItem('token')
if(!token) {return}
commit('authUser',{
token
})
dispatch('storeUser')
},
storeUser({commit, state}) {
if(!state.token) return
axios.get('/user/userInfo')
.then(res => {
if(res.data.success) {
commit('storeUser', {
name: res.data.user.name,
companyName: res.data.user.company.companyName,
role: res.data.user.role,
isPaid: res.data.user.company.stripe.isPaid,
isTrial: res.data.user.company.stripe.isTrial
})
}
})
.catch(err => {
console.log(err)
})
},
Mutations
authUser(state, userData) {
state.token = userData.token
},
storeUser(state, user) {
state.user = user
state.isPaid = user.isPaid
state.isTrial = user.isTrial
},
Looks like you have a lot of duplicate and confusing code.
Why is there 2 of isPaid and isTrial in the state?
You also did not use the name property you provided to commit function.
The commit
commit('storeUser', {
name: res.data.user.name,
isPaid: res.data.user.company.stripe.isPaid,
isTrial: res.data.user.company.stripe.isTrial
});
const store = new Vuex.Store({
state: {
userName: '',
isPaid: false,
isTrial: false,
},
mutations: {
storeUser(state, data) {
state.userName = data.name;
state.isPaid = data.isPaid;
state.isTrial = data.isTrial;
},
},
});
Now you access the state store.state.isPaid and store.state.isTrial.
You can see a working example at this jsfiddle. If you open up the console you can see how the current state is logged.

How to use all functions vue-auth without using its login function

Im new to VueJS and trying to build authorization functions for my website.
First I attempt to use library name Vue-auth to handle authorization. It works fine, here is my code:
Login.vue
login () {
var redirect = this.$auth.redirect()
this.$auth.login({
headers: {
'Content-Type': 'application/json'
},
data: this.data.body,
rememberMe: this.data.rememberMe,
redirect: {name: redirect ? redirect.from.name : 'Home'},
success (res) {
console.log('Auth Success')
},
error (err) {
console.log(err)
}
navbar ():
<div class="nav-right is-flex">
<router-link v-if="!$auth.check()" to="/login" class="nav-item">Login</router-link>
<a v-if="$auth.check()" #click="logout" class="nav-item">Logout</a>
</div>
In router, to restrict access, I use auth property. Something like:
{
path: '/users',
name: 'users',
component: require('./components/pages/Users.vue'),
meta: {auth: ['admin']}
},
{
path: '/users',
name: 'users',
component: require('./components/pages/Users.vue'),
meta: true
}
And in app.js:
Vue.use(VueAuth, {
auth: {
request: function (req, token) {
this.options.http._setHeaders.call(this, req, {Authorization: 'Bearer ' + token})
},
response: function (res) {
// Get Token from response body
return res.data
}
},
http: require('#websanova/vue-auth/drivers/http/axios.1.x.js'),
router: require('#websanova/vue-auth/drivers/router/vue-router.2.x.js'),
loginData: { url: 'http://localhost:6789/login', fetchUser: false },
refreshData: { enabled: false }
})
But now I want to write a service to call axios to API Url myself, not using $auth.login function anymore. I changed my
login () {
var self = this;
_AuthenticationService
.login(this.data.body)
.then(response => {
self.info = response;
console.log(response);
})
.catch(err => {
self.info = err;
});
My service:
import axiosconfigurator from '../axiosconfigurator'
class AuthenticationService {
login (request) {
var self = this
return new Promise((resolve, reject) => {
axios.post('https://reqres.in/api/login', {
username: 'Fred',
password: '123'
})
.then(function (response) {
// get token from this response
var token = response.data.token
self._setAuthToken(token, true)
console.log(token)
// var data = core.Parsers.UserParser.parse(response);
// history.update(data);
// deferred.resolve(history);
})
.catch(function (error) {
console.log(error)
reject(error)
});
})
}
So my question is: I dont want to use the vue-auth library login function anymore, but I still want use its advantages like $auth.ready function, or auth property in router and $auth.user. How can I achieve it?
Based on the date of your question and the fact that the library was changed lately
You can call the login method on the vue-auth object passing the following option
makeRequest:false
You have a solution described there
https://github.com/websanova/vue-auth/issues/256
this.$auth.watch.authenticated = true
this.$auth.watch.loaded = true
this.$user(response.user)
this.$router.push('/dashboard')
I tested it and it was not working so I open a ticket
https://github.com/websanova/vue-auth/issues/563

Categories

Resources