I'm trying to store the accessToken that comes back from Firebase into the Vuex store in my Vue project when I run the code I get the following error:
Uncaught TypeError: Cannot read property '$store' of undefined
It looks like it's not picking up store.js in the code below, any ideas why that might be?
Thanks
Login.vue
mounted: function() {
Firebase.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(function(idToken) {
this.$store.commit('setStoreToken', idToken)
return idToken
});
}
else {
.
.
.
}
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
loggedIn: false,
accessToken: '',
},
mutations: {
loggedIn () {
this.state.loggedIn = true
},
loggedOut () {
this.state.loggedIn = false
},
setStoreToken(state, accessToken) {
state.accessToken = accessToken
},
},
getters: {
getAccessToken: state => {
return state.accessToken
}
},
})
main.js
import store from './store.js'
new Vue({
store,
router,
render: h => h(App),
}).$mount('#app')
My guess:
user.getIdToken().then(function(idToken) {
this.$store.commit('setStoreToken', idToken)
return idToken
});
You dont use an arrow function, that means that this is binded to the function itself you can either use an arrow function or you create a variable infront of it like this const self = this
user.getIdToken().then((idToken)=>{
this.$store.commit('setStoreToken', idToken)
return idToken
});
Or you do it like this:
const self = this;
user.getIdToken().then(function(idToken) {
self.$store.commit('setStoreToken', idToken)
return idToken
});
Related
I am writing test code using firebase/auth and google login with react-native
But, there are always bug in my test code
my UI component is below.
import { GoogleSignin } from "#react-native-google-signin/google-signin";
import auth from "#react-native-firebase/auth";
let userInfo;
try {
userInfo = await GoogleSignin.signIn();
} catch (error) {
console.error(error);
}
const googleCredential = auth.GoogleAuthProvider.credential(userInfo.idToken);
let testPromise;
try {
testPromise = await auth().signInWithCredential(googleCredential);
} catch (error) {
// In real code environment, there are no problem.
// But, In my test environment, the engine of the code always occur error in here
console.error(error);
}
The error message is below.
console.error
TypeError: (0 , _auth.default) is not a function
my mocking part of the test code is below
jest.mock("#react-native-firebase/auth", () => {
return {
GoogleAuthProvider: {
credential: jest.fn().mockReturnValue({ providerId: "fakeProviderId", secret: "fakeSecret", token: "fakeToken" }),
},
signInWithCredential: jest.fn(),
};
});
some tricks are in there.
i think the way i suggests is not best practice.
but, you can do this problem like this.
In react component...
import auth from "#react-native-firebase/auth";
import { firebase } from "#react-native-firebase/auth"; << KEY POINT
test component...
const mockedFbAuth = jest.fn();
jest.mock("#react-native-firebase/auth", () => {
return {
firebase: {
auth: jest.fn().mockImplementation(() => ({
signInWithCredential: mockedFbAuth.mockReturnValue({
additionalUserInfo: {
profile: {
email: "fakeCredentialEmail",
name: "fakeCredentialName",
},
},
}),
})),
},
};
});
above this way, i can solve my problem!
i want to check if user exist before go to some pages in beforeEach method
i export the user state i use firebase v9
export const userAuthState = ()=>{
let currentUser = null;
onAuthStateChanged(auth, (user) => {
if (user) {
currentUser = user;
}
});
return currentUser;
}
here where i use it
import {userAuthState} from 'src/backend/firebase-config';
...
console.log("before route");
Router.beforeEach(async (to,from,next)=>{
if(await !userAuthState() && to.meta.requiresAuth){
next({path: 'login', query:{ redirect: to.fullPath }})
}else if(await userAuthState() && to.meta.requiresAuth == 'login'){
next({path:'/'})
}else{
next()
}
})
here the problem cant navigate to any page and print the console.log many times
how i can check the user before route in correct way
thank you.
I'll give you a simple example of how can you make some decision based on user authentication.
For this, I'll use Vuex as a central store since you'll commonly use user information across all your app. I'll assume that you're building an SPA with Vue and Firebase V9.
This is a simple store for users. Register this store with Vue (with .use()) in your main.js file (your entry point file).
import { createStore } from 'vuex'
const Store = createStore({
state() {
return {
user: {
uid: '',
email: ''
}
}
},
mutations: {
setUser (state, payload) {
if (payload) {
state.user.uid = payload.uid
state.user.email = payload.email
return
}
state.user.uid = ''
state.user.email = ''
}
}
})
export Store
Now, at your App.vue (or your root component) you simple call onAuthStateChanged and run commits depending on User's state:
<template>
<div>Your wonderful template...</div>
</template>
<script>
import { onAuthStateChanged } from "firebase/auth";
import { yourAuthService } from 'yourFirebaseInit'
export default {
name: 'App',
created () {
onAuthStateChanged(yourAuthService, (user) => {
if (user) {
this.$store.commit('setUser', { uid: user.uid, email: user.email })
} else {
this.$store.commit('setUser', null)
}
})
}
}
</script>
Finally, in your routes, you could do something like:
// we import the Store that we've created above.
import { Store } from 'your-store-path'
Router.beforeEach((to,from,next)=>{
if(to.meta.requiresAuth && Store.state.user.uid === ''){
next({path: 'login', query:{ redirect: to.fullPath }})
} else{
next()
}
})
This is a simple example of how can you implement an Authentication flow with Vue and Firebase V9.
I am creating a login page and dashboard for the admin panel using NExtjS and react-redux. Below is the code I have tried. If I login using Id and password I can login and get all the values from the state and everything works fine.
The problem is if I tried to access the dashboard URL directly it says
Cannot read properties of null (reading 'name') how can I redirect the user to the login page instead of getting up to return statement ???
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
const Dashboard = () => {
const { auth } = useSelector((state) => state);
const router = useRouter();
console.log(auth)
// I can get all the objects from state and cookies are set as state for browser reload so everything is fine here.
useEffect(() => {
if (!auth.userInfo && auth.userInfo.role == 'user') {
router.push('/admin');
console.log('I am here');
}
}, []);
return <h1>{auth.userInfo.name}</h1>;
};
export default dynamic(() => Promise.resolve(Dashboard), { ssr: false });
Finally I find the correct way of solving this issue. The correct way was:
export const getServerSideProps = async (context) => {
const session = await getSession({ req: context.req });
if (session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
return {
props: {
session,
},
};
};
Im trying to implement the authy-node phone verification with firebase functions and my app in react-native the message is sent to the correct mobile phone but for some reason the data I get back from the api is null any ideas out there
My Api firebase functions
import * as functions from 'firebase-functions';
const authy = require('authy')('mySecret');
export const getCode = functions.https.onCall((data, context) => {
const {
number, countryCode
} = data;
return authy.phones().verification_start(number, countryCode, { via:
'sms', locale: 'en', code_length: '4' }, (err: any, res: any) => {
if (err) {
throw new functions.https.HttpsError(err);
}
return res;
});
});
and this is my call from my app
export default class test extends Component {
constructor() {
super();
}
componentWillMount() {
const getCode = firebase.functions().httpsCallable('getCode');
getCode({number: 'theCorrectNumber', countryCode: '44'})
.then(function (result) {
const data = result;
console.log(data)
}).catch( function (error){
console.log(error)
})
}
render() {
return (
<View/>
);
}
}
Twilio developer evangelist here.
From what I can see in the Authy Node library that I'm assuming you're using, making a request to the API does not return a Promise. Instead it is built with request and responds to asynchronous requests using callbacks only. You do deal with the callback, but you are returning the result of calling the asynchronous function, which is null, rather than the result from the callback.
Perhaps including a callback as part of the function call would work better:
import * as functions from 'firebase-functions';
const authy = require('authy')('mySecret');
export const getCode = functions.https.onCall((data, callback) => {
const { number, countryCode } = data;
return authy
.phones()
.verification_start(
number,
countryCode,
{ via: 'sms', locale: 'en', code_length: '4' },
callback
);
});
You can then use it like this:
export default class test extends Component {
constructor() {
super();
}
componentWillMount() {
const getCode = firebase.functions().httpsCallable('getCode');
getCode({ number: 'theCorrectNumber', countryCode: '44' }, (err, res) => {
if (err) {
throw new functions.https.HttpsError(err);
}
const data = res;
console.log(data);
});
}
render() {
return <View />;
}
}
Let me know if that helps at all.
I have a middleware that can go to the refresh token before the next action runs and then run the other action when the access token expires.
But if I make more than one request at a time and the access token is over, I am trying to get as much refresh token as I am requesting. I am checking the isLoading property in state to prevent this. But after the request, isLoading value is true in the reducer, it seems to be false in the middleware, so it requests again and again.
I am sending refreshTokenPromise in fetching_refresh_token action, but I never get state.refreshTokenPromise, it is always undefined.
I definitely have a problem with the state.
So here is my question, how can I access the changing state value in middleware?
Refresh token middleware: (this version hits the endpoint multiple times)
import { AsyncStorage } from 'react-native';
import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
import moment from 'moment';
import Api from '../lib/api';
import {
FETCHING_REFRESH_TOKEN,
FETCHING_REFRESH_TOKEN_SUCCESS,
FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
export default function tokenMiddleware({ dispatch, getState }) {
return next => async (action) => {
if (typeof action === 'function') {
const state = getState();
if (state) {
const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
if (expiresIn && isExpired(JSON.parse(expiresIn))) {
if (!state.refreshToken.isLoading) {
return refreshToken(dispatch).then(() => next(action));
}
return state.refreshTokenPromise.then(() => next(action));
}
}
}
return next(action);
};
}
async function refreshToken(dispatch) {
const clientId = await AsyncStorage.getItem('CLIENT_ID');
const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');
const userObject = {
grant_type: 'refresh_token',
client_id: JSON.parse(clientId),
client_secret: JSON.parse(clientSecret),
refresh_token: refreshToken1,
};
const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');
const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));
dispatch({
type: FETCHING_REFRESH_TOKEN_SUCCESS,
data: res,
});
return res ? Promise.resolve(res) : Promise.reject({
message: 'could not refresh token',
});
}).catch((err) => {
dispatch({
type: FETCHING_REFRESH_TOKEN_FAILURE,
});
throw err;
});
dispatch({
type: FETCHING_REFRESH_TOKEN,
refreshTokenPromise,
});
return refreshTokenPromise;
}
function isExpired(expiresIn) {
return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
}
Refresh token reducer:
import {
FETCHING_REFRESH_TOKEN,
FETCHING_REFRESH_TOKEN_SUCCESS,
FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
const initialState = {
token: [],
isLoading: false,
error: false,
};
export default function refreshTokenReducer(state = initialState, action) {
switch (action.type) {
case FETCHING_REFRESH_TOKEN:
return {
...state,
token: [],
isLoading: true,
};
case FETCHING_REFRESH_TOKEN_SUCCESS:
return {
...state,
isLoading: false,
token: action.data,
};
case FETCHING_REFRESH_TOKEN_FAILURE:
return {
...state,
isLoading: false,
error: true,
};
default:
return state;
}
}
In the meantime, when I send it to the getState to refreshToken function, I get to the changing state value in the refreshToken. But in this version, the refresh token goes to other actions without being refreshed.
Monkey Patched version: (this version only makes 1 request)
import { AsyncStorage } from 'react-native';
import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
import moment from 'moment';
import Api from '../lib/api';
import {
FETCHING_REFRESH_TOKEN,
FETCHING_REFRESH_TOKEN_SUCCESS,
FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
export default function tokenMiddleware({ dispatch, getState }) {
return next => async (action) => {
if (typeof action === 'function') {
const state = getState();
if (state) {
const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
if (expiresIn && isExpired(JSON.parse(expiresIn))) {
if (!state.refreshTokenPromise) {
return refreshToken(dispatch, getState).then(() => next(action));
}
return state.refreshTokenPromise.then(() => next(action));
}
}
}
return next(action);
};
}
async function refreshToken(dispatch, getState) {
const clientId = await AsyncStorage.getItem('CLIENT_ID');
const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');
const userObject = {
grant_type: 'refresh_token',
client_id: JSON.parse(clientId),
client_secret: JSON.parse(clientSecret),
refresh_token: refreshToken1,
};
if (!getState().refreshToken.isLoading) {
const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');
const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));
dispatch({
type: FETCHING_REFRESH_TOKEN_SUCCESS,
data: res,
});
return res ? Promise.resolve(res) : Promise.reject({
message: 'could not refresh token',
});
}).catch((err) => {
dispatch({
type: FETCHING_REFRESH_TOKEN_FAILURE,
});
throw err;
});
dispatch({
type: FETCHING_REFRESH_TOKEN,
refreshTokenPromise,
});
return refreshTokenPromise;
}
}
function isExpired(expiresIn) {
return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
}
Thank you.
I solved this problem using axios middlewares. I think is pretty nice.
import { AsyncStorage } from 'react-native';
import Config from 'react-native-config';
import axios from 'axios';
import { store } from '../store';
import { refreshToken } from '../actions/refreshToken'; // eslint-disable-line
const instance = axios.create({
baseURL: Config.API_URL,
});
let authTokenRequest;
function resetAuthTokenRequest() {
authTokenRequest = null;
}
async function getAuthToken() {
const clientRefreshToken = await AsyncStorage.getItem('clientRefreshToken');
if (!authTokenRequest) {
authTokenRequest = store.dispatch(refreshToken(clientRefreshToken));
authTokenRequest.then(
() => {
const {
token: { payload },
} = store.getState();
// save payload to async storage
},
() => {
resetAuthTokenRequest();
},
);
}
return authTokenRequest;
}
instance.interceptors.response.use(
response => response,
async (error) => {
const originalRequest = error.config;
if (
error.response.status === 401
&& !originalRequest._retry // eslint-disable-line no-underscore-dangle
) {
return getAuthToken()
.then(() => {
const {
token: {
payload: { 'access-token': accessToken, client, uid },
},
} = store.getState();
originalRequest.headers['access-token'] = accessToken;
originalRequest.headers.client = client;
originalRequest.headers.uid = uid;
originalRequest._retry = true; // eslint-disable-line no-underscore-dangle
return axios(originalRequest);
})
.catch(err => Promise.reject(err));
}
return Promise.reject(error);
},
);
export default instance;
If you have a problem, do not hesitate to ask.
you could benefit from redux-sagas
https://github.com/redux-saga/redux-saga
redux-sagas is just background runner which monitors your actions and can react when some specific action is met. You can listen for all actions and react to all or you can react to only latest as mentioned in comments
https://redux-saga.js.org/docs/api/#takelatestpattern-saga-args
while redux-thunk is just another way to create actions on the go and wait for some I/O to happen and then create some more actions when I/O is done. It's more like synced code pattern and redux-sagas is more like multi-threaded. On main thread you have your app running and on background thread you have sagas monitors and reactions