Getting user email from firebase using Vue.js - javascript

I'm building a chat app with Vue.js and Firebase.
I'm new to both vue and firebase and struggeling to get the users email so i can send it to firebase to show along with the chat.
I've tried this solution:
How can i get the user in firebase database, to write from a component with vuejs?
But can't get it to work. I guess I dont really get where, how or when I can access root. Cause when i tried this.$root.something I get an error message.
This code is in my main.js file:
firebase.auth().onAuthStateChanged(function(user) {
if (!app) {
/* eslint-disable no-new */
app = new Vue({
el: '#app',
data: {email: user.email}, //here i want to store the email, which works but I cant access it from other components
template: '<App/>',
components: { App },
router
})
}
});
And this is the script in my main component. It's here I want to accses the root.
<script>
import * as firebase from 'firebase'
export default {
name: 'chat',
data: function(){
return {
room: null,
db: null, // assign Firebase SDK later
messageInput:'', // this is for v-model
messages: [],
}
},
mounted() {
this.db = firebase
// access the location and initilize a Firebase reference
this.init()
},
methods: {
init(){
this.room = this.db.database().ref().child('chatroom/1')
this.messageListener()
this.saveEmail();
},
saveEmail(){
//here i tried to save the email using the onAuthStateChanged method
firebase.auth().onAuthStateChanged(function(user) {
this.$root.email = user.email;
});
},
send(messageInput) {
//A data entry.
let data = {
message: messageInput
//here i want to add it to the database
// user: this.$root.email
};
// Get a key for a new message.
let key = this.room.push().key;
this.room.child('messages/' + key).set(data)
// clean the message
this.messageInput = ''
},
messageListener () {
this.room.child('messages').on('child_added', (snapshot) => {
// push the snapshot value into a data attribute
this.messages.push(snapshot.val())
})
},
logout(){
firebase.auth().signOut().then(() => {
this.$root.email = null;
this.$router.replace('login');
})
},
}
}
</script>
And here is the script in my login component :
<script>
import firebase from 'firebase'
export default {
name: 'login',
data: function(){
return {
email: '',
password: '',
}
},
methods: {
signIn: function(){
firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(
(user) => {
this.$root.email = user.email;
this.$router.replace('chat');
},
(err) => {
alert('Opppps! ' + err.message);
}
);
},
}
}
</script>
Sorry if I'm not being clear. Thanks in advance!

The callback of the onAuthStateChanged method is bind to the wrong this scope. You can easily fix this by using an arrow function like below. When using an arrow function, it will automatically bind to the context it is defined in.
saveEmail() {
firebase.auth().onAuthStateChanged((user) => {
this.$root.email = user.email;
})
}

Related

Not getting auth.currentUser after registration until I reload the page (VUE3 + Quasar + Firebase)

Facing a little issue in regards to user registration. Everything works fine except this one detail where I have to reload the page after user registration or else their data will not render. I am displaying the username on the Navbar once they log in/register so this is not ideal.
Here is my App.vue:
<template>
<router-view />
</template>
<script setup>
import { onMounted } from "vue";
import { useAuthStore } from "stores/auth";
const storeAuth = useAuthStore();
onMounted(() => {
storeAuth.init();
});
</script>
Here is my init() function in the auth Store:
init() {
onAuthStateChanged(auth, (user) => {
if (user) {
this.user.id = user.uid;
this.user.email = user.email;
this.user.displayName = user.displayName;
} else {
this.user = {};
}
});
and in the Registration Form component, this is the function that triggers when clicking SUBMIT:
\\ Triggering Firebase registration and opening a confirmation modal if successful.
const onSubmit = () => {
storeAuth.registerUser(credentials);
registered.value = true;
};
\\When the user clicks OK in the confirmation modal he/she is redirected to their profile page
function redirectUser() {
const id = auth.currentUser.uid;
router.push({ name: "user", params: { id } });
}
and finally, this is the registerUser method in the auth Store:
registerUser(credentials) {
createUserWithEmailAndPassword(
auth,
credentials.email,
credentials.password
)
.then((userCredential) => {
console.log(userCredential);
const user = userCredential.user;
updateProfile(auth.currentUser, {
displayName: credentials.displayName,
});
setDoc(doc(db, "users", auth.currentUser.uid), {
displayName: credentials.displayName,
email: credentials.email,
countryCode: credentials.countryCode,
phoneNumber: `+${credentials.countryCode.value}${credentials.phoneNumber}`,
userType: "Persona",
uid: auth.currentUser.uid,
});
})
.catch((error) => {
console.log("error.message: ", error.message);
});
}
Any pointers would be greatly appreciated. Basically I want to avoid having to refresh the page for the displayName to appear in the NavBar.
The default value of user.diplayName name is null. Although you are using updateProfile() to set a value, but you are not handling the promises correctly. First, try refactoring the code as shown below:
// async function
async registerUser(credentials) {
const { user } = await createUserWithEmailAndPassword(auth, credentials.email, credentials.password)
await updateProfile(user, {
displayName: credentials.displayName
});
await setDoc(doc(db, "users", auth.currentUser.uid), {
displayName: credentials.displayName,
email: credentials.email,
countryCode: credentials.countryCode,
phoneNumber: `+${credentials.countryCode.value}${credentials.phoneNumber}`,
userType: "Persona",
uid: auth.currentUser.uid,
});
}
The profile may not update right away. You can use reload() to reload current user's data.
// add this after updateProfile()
await reload(user);
You are updating the state from inside of onAuthStateChanged() that'll trigger right after user is signed in. It'll be best to update the displayName in state manually in case of registration after updateProfile().

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.

How do I refactor this Vue Js code to avoid using local storage?

I have a code that handles authentication for a Vue.js and Auth0 application. It stores and retrieves values in local storage. How do I change this code so as to access the values expiresAt, idToken, accessToken and user directly instead of using local storage?
import auth0 from 'auth0-js'
import Vue from 'vue'
let webAuth = new auth0.WebAuth({
domain: 'your_auth0_domain',
clientID: 'your_auth0_client',
redirectUri: 'http://localhost:8080/callback',
audience: 'https://' + 'your_auth0_domain' + '/api/v2/',
responseType: 'token id_token',
scope: 'openid profile' // define the scopes you want to use
})
let auth = new Vue({
computed: {
token: {
get: function () {
return localStorage.getItem('id_token')
},
set: function (id_token) {
localStorage.setItem('id_token', id_token)
}
},
accessToken: {
get: function () {
return localStorage.getItem('access_token')
},
set: function (accessToken) {
localStorage.setItem('access_token', accessToken)
}
},
expiresAt: {
get: function () {
return localStorage.getItem('expires_at')
},
set: function (expiresIn) {
let expiresAt = JSON.stringify(expiresIn * 1000 + new Date().getTime())
localStorage.setItem('expires_at', expiresAt)
}
},
user: {
get: function () {
return JSON.parse(localStorage.getItem('user'))
},
set: function (user) {
localStorage.setItem('user', JSON.stringify(user))
}
}
},
methods: {
login() {
webAuth.authorize()
},
logout() {
return new Promise((resolve, reject) => {
localStorage.removeItem('access_token')
localStorage.removeItem('id_token')
localStorage.removeItem('expires_at')
localStorage.removeItem('user')
webAuth.authorize()
})
},
isAuthenticated() {
return new Date().getTime() < this.expiresAt
},
handleAuthentication() {
return new Promise((resolve, reject) => {
webAuth.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.expiresAt = authResult.expiresIn
this.accessToken = authResult.accessToken
this.token = authResult.idToken
this.user = authResult.idTokenPayload
resolve()
} else if (err) {
this.logout()
reject(err)
}
})
})
}
}
})
export default {
install: function (Vue) {
Vue.prototype.$auth = auth
}
}
Use vuex store
Once you get the token from the end point you can store it to local storage:
api_call_here
.then(response => {
localStorage.setItem('token', response.body.token)
})
Next, your vuex store should look like:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
isLogged: !!localStorage.getItem('token')
token: localStorage.getItem('token') || null
}
In that way you will be able in every component to check if user is logged by:
this.$store.state.isLogged //=> will return true or false
You can follow the same logic for access token and expires at.
Update: The SPA applications can handle everything without need of refresh. But after reload (manually) the variables will not hold their own state.
That's why you are using local Storage, so even if the page reloads the token is saved in local Storage and you can retrieve it.
Practically when the user logs in, you save the token in localStorage.Whenever
the page reloads, the user stays logged until the token is in localStorage.
If you just put the token in a variable, if the page reloads this variable will not hold the token anymore.
If you don't like the localStorage, as solution, you can send a login request whenever the page reloads which is not recommended.
I want to mention that you can also use cookies.
Hope my answer helped you.

Component variable can't be modified in a callback method in vue.js

I defined a Login.vue component in the vue.js that let's me login the user to my application through AWS Cognito. I use the ___.authenticateUser() method to login the user and start a session with Cognito. Below is the actual code that does so:
export default {
name : 'Login',
data: function() {
return {
errorMessageHidden: true,
formHidden: false,
errorMessage: '',
email: '',
password: ''
}
},
methods: {
showValuesToMe: function() {
console.log(this.errorMessageHidden)
},
handleLogin: function() {
const data = {
Username: this.email,
Pool: userPool
}
const cognitoUser = new CognitoUser(data);
const authenticationData = {
Username : this.email,
Password : this.password,
};
function showErrorMessage(err) {
this.errorMessageHidden = false;
this.errorMessage = err.message;
console.log("The method got called... errorMessageHidden = " + this.errorMessageHidden);
}
const authenticationDetails = new AuthenticationDetails(authenticationData)
cognitoUser.authenticateUser(authenticationDetails, {
callback: showErrorMessage,
onSuccess: function(result) {
console.log('access token ' + result.getAccessToken().getJwtToken());
},
onFailure: function(err) {
this.callback(err);
}
});
},
hideErorrMessage: function() {
this.errorMessageHidden = true;
}
}
}
The issue is, inside the handleLogin() function of the component, when ___.authenticateUser() is called Cognito SDK calls either onSuccess() or onFailure() depending on the auth result from the AWS.
Inside the onFailure() when I try to modify the errorMessageHidden and errorMessage they can't be! It happens because the onFailure() method is a callback method and a closure.
To access/modify these values I defined the function showErrorMessage(err) {...} in the scope of closure's parent. Now I can access the values defined in data but still can't modify them.
Can anyone figure it out how I can change the values to make changes and show error in the browser.
Your problem is because you are using function instead of arrow functions for your callback functions. When you create a function using function a new scope is created and this is not your Vue Component anymore.
You want to do it like this:
handleLogin: function() {
const data = {
Username: this.email,
Pool: userPool
}
const cognitoUser = new CognitoUser(data);
const authenticationData = {
Username : this.email,
Password : this.password,
};
const showErrorMessage = err => {
this.errorMessageHidden = false;
this.errorMessage = err.message;
console.log("The method got called... errorMessageHidden = " + this.errorMessageHidden);
}
const authenticationDetails = new AuthenticationDetails(authenticationData)
cognitoUser.authenticateUser(authenticationDetails, {
callback: showErrorMessage,
onSuccess: result => {
console.log('access token ' + result.getAccessToken().getJwtToken());
},
onFailure: err => {
this.callback(err);
}
});
}
With arrow functions, you will maintain the scope of the function that is calling it, so if you are inside a Vue method and use an arrow function, inside the arrow function this will still be the Vue Component.
Just beware, you cannot use arrow functions as a direct property of the methods object. That is because Vue needs to invoke the function with the Vue Component bound to this, something that cannot be done with arrow functions. But besides that, you may want to start using arrow function everywhere you can, they are one of my favorite ES5 features.
This worked for me:
Function in the this components:
BusEvent.$emit("CloseAllTab",check => {
if(check == true){
this.isShowSelectYearActive = true;
}
});
Function in the other components:
methods: {
CloseAllTab(check) {
check(true);
}
},
created() {
BusEvent.$on("CloseAllTab",this.CloseAllTab);
},
beforeDestroy() {
BusEvent.$on("CloseAllTab",this.CloseAllTab);
}

Categories

Resources