Get UserName and Make UserName reactive after Vue Authentication - vue.js

I am having trouble getting user data and making user data reactive after the user has logged In.
Without using Store i am getting the user information but I am unable to make it reactive. So I tried storing user information in store. Now I am having getting that data as well.
I have a login form in LOGINCOMPONENT.VUE that has two input fields email and password.
<form #submit.prevent="login">
<input placeholder="Email" type="email" v-model="formData.email">
<input placeholder="Password" type="password" v-model="formData.password">
</form>
Script portion:
export default {
name: 'LoginPage',
data() {
return {
formData: {},
};
},
methods: {
login() {
this.$axios.post('login', this.formData).then(async (res) => {
await localStorage.setItem('user', JSON.stringify(res));
await localStorage.setItem('token', res.token);
this.$router.push('/');
console.log(res);
this.$store.dispatch('userDataAction', res); --->>> Using Store to take user data
}).catch((error) => {
console.log(error);
});
},
},
};
Login process goes well and user token is generated.
This is my store.
const state = {
token: localStorage.getItem('token') || null,
userData: {},
};
const getters = {
getUserData: state => state.userData,
loggedIn: state => state.token != null,
};
const mutations = {
userDataMutation(state, userData) {
state.userData = userData;
},
};
const actions = {
userDataAction(context, credentials) {
const userData = {
username: credentials.username,
email: credentials.email,
firstName: credentials.first_name,
lastName: credentials.last_name,
};
context.commit('userDataMutation', userData);
},
};
Finally in my HEADERCOMPONENT.VUE where i am showing "SIGN IN" if user is not logged In and "HELLO USERNAME" if user is logged in.
export default {
name: 'HeaderComponent',
computed: {
...mapGetters(['getUserData', 'loggedIn']),
},
};
Template:
<div> {{ loggedIn ? getUserData.username : 'Sign In' }} </div>

Related

How do I connect NuxtJS login auth to my backend with axios?

I currently have a frontend auth app somewhat made with NuxtJS, where it will take in username/password fields and then use this.$auth.login to login.
I'm confused, though, on how to pass this info to the backend so it can verify that the username/password combination is correct. Currently my code will direct to the next page no matter what I put in the fields (makes sense since I haven't configured anything yet). I understand I need to use Axios POST requests somehow and I made an attempt at that but I don't really know what to do next. I don't know how to grab the token that contains my user data and push it to my backend (adonisJS) so I can check it against the database.
My login.vue component
<template>
<div>
<v-form #submit.prevent="loginUser">
<div>
<v-label>Username</v-label>
<v-text-field color='red' v-model="login.username" />
</div>
<div>
<v-label>Password</v-label>
<v-text-field color='red' v-model="login.password" />
</div>
<div>
<v-btn type="submit" color='purple'>Submit</v-btn>
</div>
</v-form>
</div>
</template>
<script>
export default {
data() {
return {
login: {
username: '',
password: ''
}
}
},
methods: {
async loginUser() {
const response = await this.$axios.post("/auth/users", {
email: this.email,
password: this.password,
}).then(
await this.$auth.login({
data: {
email: this.email,
password: this.password
}
}).then(() => {
this.$router.push('/dashboard')
}).catch(err => {
console.log(err)
})
).catch(err => console.log(err));
}
}
}
</script>
My nuxt.js.config (relevant parts)
axios: {
baseURL: 'http://localhost:3000/api', // Used as fallback if no runtime config is provided
},
auth: {
strategies: {
local: {
token: {
property: 'access_token',
required: true,
type: 'Bearer'
},
user: {
property: false, // <--- Default "user"
autoFetch: true
},
endpoints: {
login: { url: '/auth/login', method: 'post' },
logout: { url: '/auth/logout', method: 'post' },
user: { url: '/user', method: 'get' }
}
}
}
},
router: {
middleware: ['auth']
},
Can anyone help me out with what I need to do with axios? (I checked my storage to see if there was a token there and it just says "false".) Thank you!
You don't need to use axios post, this is how you should log in
methods: {
async loginUser() {
try {
//this will attempt to log in, and if successful it'll go to the home page unless you change the default redirects
const fields = {email: this.email, password: this.password};
await this.$auth.loginWith("local", { data: fields });
console.log("login successful");
} catch (errors) {
//an error occurred, could be wrong password or any other type of error
console.error(errors);
}
}
}
local is the strategy name and means that it's your own auth server, if you were using google for example you'd put google there, check out loginwith and local
the auth library already knows that your login url is /auth/login since you stated that in the nuxt config.
if you want to control where it goes after the login, look into the redirect options in the nuxt.config from the docs and specifically in this case home.
auth: {
redirect: {
home: '/'
}
}

How to wait for state change before executing next action in Vuex

I'm trying to login and get user name at the same time. Here is how it works:
First action login gets access token and updates state.accessToken
Using state.accessToken I need to get user data at the same time (fetchUser) when login is pressed, however when fetchUser is executed state.accessToken is still null as actions are asynchronous. What is the best practice to wait for state change before executing the next action? I tried to look up for examples, but solutions found are not applicable for my case.
store.js
const store = new Vuex.Store({
state: {
accessToken: null,
user: null
},
mutations: {
authUser (state, userData) {
state.accessToken = userData.accessToken
},
storeUser (state, user) {
state.user = user
}
}
actions: {
login({commit}, authData) {
axios.post("http://example.com/token/create/", {
email: authData.email,
password: authData.password
})
.then(res => {
commit('authUser', {
accessToken: res.data.access
})
})
},
fetchUser({commit, state}) {
axios.get("http://example.com/api/auth/v1/me/", {
headers: {Authorization: "Bearer " + state.accessToken}
})
.then(res => {
commit('storeUser', res.data.user)
})
}
}
getters: {
user (state) {
return state.user
},
isAuthenticated(state) {
return state.accessToken !== null
}
}
})
login.vue
<template>
<form #submit.prevent="submitForm">
<div v-if="!auth" class="row">
<input class="col" placeholder="Email" v-model="formInfo.email" type="text"></input>
<input class="col" placeholder="Password" v-model="formInfo.password" type="password"></input>
<button class="col" type="submit" label="Log In"></button>
</div>
<div v-else class="row">
Hello {{ firstname }}
</div>
</form>
</template>
<script>
export default {
data() {
return {
formInfo: {
email: '',
password: ''
}
};
},
methods: {
submitForm() {
this.$store.dispatch('login', {email: this.formInfo.email, password: this.formInfo.password})
this.$store.dispatch('fetchUser')
}
},
computed: {
auth() {
return this.$store.getters.isAuthenticated
},
firstname() {
return this.$store.getters.user.firstname
}
}
}
};
</script>
have you tried adding await?
async submitForm() {
await this.$store.dispatch('login', {email: this.formInfo.email, password: this.formInfo.password});
await this.$store.dispatch('fetchUser');
}
it will wait for the login to finish before fetching the user
also maybe add async in your actions methods:
async login({commit}, authData) {...}
async fetchUser({commit, state}) {...}
You can dispatch an action from another action.
actions: {
login({commit, dispatch}, authData) {
axios.post("http://example.com/token/create/", {
email: authData.email,
password: authData.password
})
.then(res => {
const accessToken = res.data.access;
dispatch('fetchUser', accessToken);
commit('authUser', { accessToken })
})
},
fetchUser({commit, state}, accessToken) {
axios.get("http://example.com/api/auth/v1/me/", {
headers: {Authorization: "Bearer " + accessToken}
})
.then(res => {
commit('storeUser', res.data.user)
})
}
}
Evan's async/await solution should work.
If there are multiple pages/components that can issue actions in any order, and you need to not proceed with one action before another is complete, you could use the strategy of storing promises as state. You can then access the promise any other action depends on the completion of and await it before proceeding.
So in login, you would use async/await syntax and an extra mutation call:
const promise = axios.post("http://example.com/token/create/", {
email: authData.email,
password: authData.password
});
commit('loginPromise', promise);
const result = await promise;
You would need to add the above mutation.
Then in fetchUser, before issuing the API call:
await state.loginPromise
That would guarantee that the promise has completed. You will have to handle error scenarios of course.

The flushPromises() function does not wait for mocked axios call to be resolved when using delayResponse

I have a login component that's supposed to take user credentials, submit them to the API and based on the response either set JWT in the VueX store or show an error.
In the unit test I'm mocking axios call with a response that's delayed to simulate actual network (using axios-mock-adapter library). Currently, the test is failing, because the test finishes before the promise is resolved despite the fact that I'm using flushPromises() utility.
In fact, I do not see either of the console.log's that are in my login function.
If I remove the delayResponse property from the mocked call, the test passes and i see the 'got data' response in my console log as expected.
What am I missing here? Does anyone have any clue why the flushPromises() just skips over the axios call?
I'm using Vuetify framework, hence the v- tags, although I doubt that makes a difference for the test
Template:
<v-alert
type="error"
:value="!!error"
test-id="LoginError"
v-html="error"
/>
<form
#submit.prevent="login"
test-id="LoginForm"
>
<v-text-field
label="Email Address"
test-id="LoginEmailField"
v-model="credentials.email"
/>
<v-text-field
label="Password"
type="password"
test-id="LoginPasswordField"
v-model="credentials.password"
/>
<v-btn
type="submit"
test-id="LoginSubmitBtn"
>
Login
</v-btn>
</form>
The login function is fairly straight-forward:
async login() {
try {
const response = await axios.post(this.url, this.credentials);
console.log('got response', response);
const token = response.data.payload;
this.$store.dispatch('setUser', { token });
} catch (error) {
console.warn('caught error', error);
this.error = error;
}
}
Test file:
fit('Receives and stores JWT in the VueX store after sending valid credentials', async () => {
const maxios = new AxiosMockAdapter(axios, {delayResponse: 100});
const validData = { email: 'aa#bb.cc', password: '111' };
const validToken = 'valid-token';
maxios.onPost().reply(200, { payload: validToken });
wrapper.find('[test-id="LoginEmailField"]').vm.$emit('input', validData.email);
wrapper.find('[test-id="LoginPasswordField"]').vm.$emit('input', validData.password);
wrapper.find('[test-id="LoginSubmitBtn"]').trigger('click');
await flushPromises();
expect(localStore.state.user.token).toBe(validToken);
});
VueX store:
export const state = {
user: {
token: null,
},
};
const mutations = {
SET_USER: (currentState, user) => {
currentState.user = user;
},
};
const actions = {
setUser: ({ commit }, payload) => {
commit('SET_USER', payload);
},
};
export const mainStore = {
state,
mutations,
actions,
};
export default new Vuex.Store(mainStore);
The response:
expect(received).toBe(expected) // Object.is equality
Expected: "valid-token"
Received: null

Vuex 3: State is undefined

versions:
"vue": "2.4.2",
"vue-router": "2.7.0",
"vuex": "3.0.1"
I am attempting to mock a simple login with two fields that will in the end have JWT to allow an authenticated login.
However, the state in Vuex refuses to change and will output that it is "undefined"
Starting from Login.vue:
<template>
<div class="login" id="login">
<b-form-input
id="inputfield"
v-model="username"
type="text"
placeholder="username">
</b-form-input>
<b-form-input
id="inputfield"
type="password"
v-model="password"
placeholder="password">
</b-form-input>
<b-button #click="login()" id = "inputfield" variant="outline-success">
Login
</b-button>
</div>
</template>
<script>
export default {
name: 'login',
data () {
return {
username: '',
password: ''
}
},
methods: {
login () {
this.$store.dispatch('login', {
username: this.username,
password: this.password,
isAuthed: true // this is a temp test to see if it changes
})
}
}
}
</script>
The store is as such:
export default new Vuex.Store({
state: {
username: null,
loggedIn: false
},
mutations: {
authUser (state, userData) {
console.log(userData.isAuthed) // True!
state.username = userData.username
state.loggedIn = userData.isAuthed
console.log(state.loggedIn) // Undefined?
console.log(state.username) // Also undefined?
}
},
actions: {
login ({commit}, authData) {
console.log(authData)
commit('authUser', {
authData
})
}
})
In another words, I can follow the isAuthed and username around the flow and they're always present, everything goes wrong when trying to assign a new value to the state. Am I doing it wrong? This is following a guide, however vuex is version 3 here, did they change the way you mutate the state?
The error is here in your login action.
commit('authUser', {
authData
})
That should just be
commit('authUser', authData)
In the original statement you are creating a new object with an authData property. What you want to do is simply pass the authData object to the mutation.
Here is a demonstration of it working.

navigation gaurd not working properly

I store the log in status of the user in my store.js (using vuex for state management)
When the user is logged in the login status is set to true in store.js
I check if the user is logged in and using v-if i hide the login button . Till he everything works fine
Now for checking purpose i removed the v-if condition on login button
I set up á before enter navigation guard in my !ogin.vue component as below
login.vue
beforeRouteEnter(to, from, next){
next(vm => {
if(vm.$store.getters.g_loginStatus === true){
next('/');
}else{
next();
}
})
}
If the user is logged in and presses the login button he is redirected to the home page
This works fine as the navigation guard is set up.
but the problem arises when i directly type in the login component url (localhost:8080/login) in the search.
The login component gets loaded normally without getting redirected to home page...
Why does this happen¿ Am i doing something wrong
I enen tried another approach using route meta fields following the documentation at route meta fields
But same problem
when i type the direct url to login component in search not getting redirected
import Vue from 'vue'
import Vuex from 'vuex'
import * as firebase from 'firebase'
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
loggedIn: false,
userName: 'Guest',
error: {
is: false,
errorMessage: ''
},
toast: {
is: false,
toastMessage: ''
}
},
getters: {
g_loginStatus: state => {
return state.loggedIn;
},
g_userName: state => {
return state.userName;
},
g_error: state => {
return state.error;
},
g_toast: (state) => {
return state.toast;
}
},
mutations: {
m_logInUser: (state) => {
state.loggedIn = true;
},
m_loggedOut: (state) => {
state.loggedIn = false;
}
},
actions: {
a_logInUser: ({state, dispatch}, user) => {
return new Promise((resolve, reject) => {
firebase.auth().signInWithEmailAndPassword(user.e, user.p).then(
() =>{
resolve(dispatch('a_authStateObserver'));
}, error => {
state.error.is = true;
let errorCode = error.code;
let errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
state.error.errorMessage = 'Wrong password.';
} else {
state.errorMessage = errorMessage;
}
}
);
});
},
a_loggedOut: () => {
firebase.auth().signOut().then(() => {
dispatch('a_authStateObserver');
});
},
a_signUpUser: ({state, dispatch}, user) => {
return new Promise((resolve, reject) => {
firebase.auth().createUserWithEmailAndPassword(user.e, user.p).then(
(u) =>{
let uid = u.uid;
resolve(dispatch('a_authStateObserver'));
}, error => {
state.error.is = true;
let errorCode = error.code;
let errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
state.error.errorMessage = 'Wrong password.';
} else {
state.errorMessage = errorMessage;
}
}
);
});
},
a_authStateObserver: ({commit, state}) => {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
var displayName = user.displayName;
state.userName = user.email;
state.error.is = false;
commit('m_logInUser');
} else {
// User is signed out.
commit('m_loggedOut');
}
});
}
}
});
login.vue
<template>
<div class="container">
<div class="row">
<div class="form_bg center-block">
<form #submit.prevent="loginUser">
<h3 class="text-center">Log in</h3>
<br/>
<div class="form-group">
<input v-model="email" type="email" class="form-control" placeholder="Your Email">
</div>
<div class="form-group">
<input v-model="password" type="password" class="form-control" placeholder="Password">
</div>
<div class="align-center">
<p class="error" v-if="g_error.is">{{ g_error.errorMessage }}</p>
<button type="submit" class="btn btn-success center-block">Log in</button>
</div>
</form>
<br>
<p style="display:inline-block">Don't have an account?</p>
<router-link to="/signup" tag="a" style="display:inline-block">Sign up</router-link>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default{
data(){
return{
email: '',
password: ''
};
},
methods: {
loginUser(){
this.$store.dispatch('a_logInUser', {e: this.email, p: this.password}).then(() =>{
this.$router.replace('/statuses');
});
}
},
computed: {
...mapGetters([
'g_error'
])
},
beforeRouteEnter(to, from, next){
next(vm => {
console.log(vm.$store.getters.g_loginStatus);
if(vm.$store.getters.g_loginStatus === true){
next('/');
}else{
next();
}
})
}
}
**routs.js**
import Home from './components/Home.vue'
import Users from './components/user/Users.vue'
import Statuses from './components/user/Statuses.vue'
import Post from './components/Post.vue'
import UserStatus from './components/user/UserStatus.vue'
import Signup from './components/auth/Signup.vue'
import Login from './components/auth/Login.vue'
export const routes = [
{path: '/', component: Home, name:'home'},
{path: '/users', component: Users, name:'users'},
{path: '/statuses', component: Statuses, name:'statuses'},
{path: '/current', component: UserStatus, name:'currentUser'},
{path: '/signup', component: Signup, name:'signup'},
{path: '/login', component: Login, name:'login'},
{path: '/post', component: Post}
];