Unusual redirects - firebase

I create an application with Vue.js, vue-router and firebase. In the main.js file I put this code:
created() {
firebase.initializeApp({
apiKey: "xxxxxxxxxxx",
authDomain: "xxxxxxx.firebaseapp.com",
databaseURL: "https://xxxxxxxxxx.firebaseio.com",
projectId: "xxxxxxxxxx",
storageBucket: "",
})
firebase.auth().onAuthStateChanged((user) => {
if(user) {
this.$store.dispatch('autoSignIn', user)
}
})
}
This is a Vue-Store:
state: {
user: null
},
mutations: {
setUser(state, payload) {
state.user = payload
}
},
actions: {
autoSignIn({commit}, payload) {
commit('setUser', {
id: payload.uid
})
}
}
In the vue-router, I made sure that before proceeding to the root directory, it checked whether the user was authorized. If not, redirect it to the login page. And in the login page, it checks whether the user is authorized. If not then redirects to the root directory. So, I'm logged in and I'm in the root directory. I reload the page and I immediately redirect to the login page and immediately redirected to the root directory and nothing else happens. All this happens in 1 second, and looks ugly.

firebase.auth().onAuthStateChanged() is asynchronous. So by the time we are able to access the user the route guard is being executed . That's the reason you are being redirected to the login page.
By that time the async call has finished we have access to the user. Since you are checking for user in the login page and now that the user is available you are kicked back to root route.
Firebase by default stores a token in localStorage under the key 'firebase:authUser' for keeping the auth state persisted .
So you can check whether this key is present in localStorage in your route guard
router.beforeEach((to, from, next) => {
//check local storage
let loccalStorageKeys = Object.keys(window.localStorage);
const firebaseAuthUser = loccalStorageKeys.filter(item => item.startsWith('firebase:authUser'))[0];
if(firebaseAuthUser){
next();
}else{{
next('/login');
}}
})
Since the check is synchronous , it should prevent the jumping redirects.
Or the alternative would be to persist the vuex state corresponding to the user using a vuex plugin called vuex-persistedstate. Then check for the user state for guarding the routes. Then when the user logs out clear the user state. Check out this post regarding vuex-persisted state.
NOTE:
As of firebase v4.12.0 the auth state persistance(for LOCAL persistance) is moved from local storage to indexedDB.
As this is an internal implementation of firebase and may change in future, so relying on checking whether firebase:authUser is present in local storage to confirm user's login status is not a good idea.
So it would be better to persist the user data as mentioned in the alternative method above and use that to check the user is logged in or not

Related

NuxtJS state changes and firebase authentication

I am still a nuxt beginner, so please excuse any faults.
I am using the "official" firebase module for nuxt https://firebase.nuxtjs.org/ to access firebase services such as auth signIn and singOut.
This works.
However, I am using nuxt in universal mode and I cannot access this inside my page fetch function. So my solution is to save this info in the vuex store and update it as it changes.
So, once a user is logged in or the firebase auth state changes, a state change needs to happen in the vuex store.
Currently, when a user logs in or the firebase auth state changes, if the user is still logged in, I save the state to my store like so :
const actions = {
async onAuthStateChangedAction(state, { authUser, claims }) {
if (!authUser) {
// claims = null
// TODO: perform logout operations
} else {
// Do something with the authUser and the claims object...
const { uid, email } = authUser
const token = await authUser.getIdToken()
commit('SET_USER', { uid, email, token })
}
}
}
I also have a mutation where the state is set, a getter to get the state and the actual state object as well to store the initial state:
const mutations = {
SET_USER(state, user) {
state.user = user
}
}
const state = () => ({
user: null
})
const getters = {
getUser(state) {
return state.user
}
}
My problem is, on many of my pages, I use the fetch method to fetch data from an API and then I store this data in my vuex store.
This fetch method uses axios to make the api call, like so:
async fetch({ store }) {
const token = store.getters['getUser'] //This is null for a few seconds
const tempData = await axios
.post(
my_api_url,
{
my_post_body
},
{
headers: {
'Content-Type': 'application/json',
Authorization: token
}
}
)
.then((res) => {
return res.data
})
.catch((err) => {
return {
error: err
}
console.log('error', err)
})
store.commit('my_model/setData', tempData)
}
Axios needs my firebase user id token as part of the headers sent to the API for authorization.
When the fetch method runs, the state has not always changed or updated yet, and thus the state of the user is still null until the state has changed, which is usually about a second later, which is a problem for me since I need that token from the store to make my api call.
How can I wait for the store.user state to finish updating / not be null, before making my axios api call inside my fetch method ?
I have considered using cookies to store this information when a user logs in. Then, when inside the fetch method, I can use a cookie to get the token instead of having to wait for the state to change. The problem I have with this approach is that the cookie also needs to wait for a state change before it updates it's token, which means it will use an old token upon the initial page load. I might still opt for this solution, it just feels like it's the wrong way to approach this. Is there any better way to handle this type of conundrum ?
Also, when inside fetch, the first load will be made from the server, so I can grab the token from the cookie, however the next load will be from the client, so how do I retrieve the token then if the store value will still be null while loading ?
EDIT:
I have opted for SPA mode. After thinking long and hard about it, I don't really need the nuxt server and SPA mode has "server-like" behaviour, where you could still use asyncdata and fetch to fetch data before pages render, middleware still works similar and authentication actually works where you dont have to keep the client and server in sync with access tokens etc. I would still like to see a better solution for this in the future, but for now SPA mode works fine.
I came across this question looking for a solution to a similar problem. I had a similar solution in mind as mentioned in the other answer before coming to this question, what I was looking for was the implementation details.
I use nuxt.js, the first approach that came to my mind was make a layout component and render the <Nuxt/> directive only when the user is authenticated, but with that approach, I can have only one layout file, and if I do have more than one layout file I will have to implement the same pre-auth mechanism across every layout, although this is do-able as now I am not implementing it in every page but implementing in every layout which should be considerably less.
I found an even better solution, which was to use middlewares in nuxt, you can return a promise or use async-await with the middleware to stop the application mounting process until that promise is resolved. Here is the sample code:
// middleware/auth.js
export default async function ({ store, redirect, $axios, app }) {
if (!store.state.auth) { // if use is not authenticated
if (!localStorage.getItem("token")) // if token is not set then just redirect the user to login page
return redirect(app.localePath('/login'))
try {
const token = localStorage.getItem("token");
const res = await $axios.$get("/auth/validate", { // you can use your firebase auth mechanism code here
headers: {
'Authorization': `Bearer ${token}`
}
});
store.commit('login', { token, user: res.user }); // now just dispatch a login action to vuex store
}
catch (err) {
store.commit('logout'); // preauth failed, so dispatch logout action which will clear localStorage and our Store
return redirect(app.localePath('/login'))
}
}
}
Now you can use this middleware in your page/layout component, like so:
<template>
...
</template>
<script>
export default {
middleware: "auth",
...
}
</script>
One way of fixing this is to do the firebase login before mounting the app.
Get the token from firebase, save it in vuex and only after that mount the app.
This will ensure that by the time the pages load you have the firebase token saved in the store.
Add checks on the routes for the pages that you don't want to be accessible without login to look in the store for the token (firebase one or another) and redirect to another route if none is present.

User redirect and authentication with middleware of Nuxt

I'm trying to redirect a user to a login page if the user is not logged in when he tries to access certain pages with the following code.
// middlware/authenticated.js
import firebase from 'firebase'
export default function ({ store, redirect }) {
let user = firebase.auth().currentUser
store.state.user = user //this doesn't work
if (!user) {
console.log('redirect')
return redirect('/login')
}
}
However, the problem is with this code when I refresh a page I'm redirected to login page although without using the middleware, I can stay in the same page with logged in. For some reasons, which I don't know why, firebase can't work in middleware.
How should I modify this middleware or implement this function?
Thanks.
//middleware/authenticated.js
export default function ({
store,
redirect
}) {
if (!store.getters['index/isAuthenticated']) {
return redirect('/login')
}
}
//post.vue
async mounted () {
if (process.browser) {
let user;
if (!this.user) user = await auth(); // this auth is a plugin
await Promise.all([
this.user ? Promise.resolve() : this.$store.dispatch("setUser", { user: user || null })
]);
this.isLoaded = true;
}
},
//plugins/auth.js
import firebase from '~/plugins/firebase'
function auth () {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => {
resolve(user || false)
})
})
}
export default auth
By default Firebase persists the users logged in status on successful authentication. This example uses the session, to store the user uid and cookies to store the users token and used in situations where the sessions has ended (example when browser is closed) and then a new session started but where the user is still authenticated according to Firebase. In cases like these the user will not need to sign in to view protected resources.
Your basic Middleware to protect it should look like this (if you have a Store Module called User)
export default function ({ store, redirect }) {
if (!store.getters['modules/user/isAuthenticated']) {
return redirect('/auth/signin')
}
}
In your main Store you use the ServerInit Function to get the User if there is one saved in the Cookies and load it into your User Store Module which will be used for verification in the Middleware.
Your User Store Module should look like this, and keep in mind that you remove the Cookie when you Log the User out so that he is fully logged out.
I used the things i mentioned above as the beginning of my Authentication and modified it a bit, which you can also do. Most of the credit goes to davidroyer who has set up this nice Github Repo which includes all needed files as a good example on how to accomplish your goal.

Logout user when app redirect to another app Meteor JS

I have 2 apps, one for auth while the other for products. After login or verification of email, the user is redirected to the products app. On redirection, the recently logged in user is instantly logged out, so a reference to the logged in user becomes null, and I'll need the logged in credentials in the auth app to authenticate on the second. How do I maintain the logged in state in the auth app when it has redirected to the products app?
This is the login function on the auth app
var callLogin = function (email, password, router) {
Meteor.loginWithPassword(email, password, ( error )=> {
if (error) {
sAlert.error( error );
} else {
sAlert.success("Logged in successfully");
window.location.replace( "http://localhost:3300/" + Meteor.userId() );
}
});
}
This is the onCreated function on the products app
Tracker.autorun(function () {
let router = FlowRouter.getParam("_id");
let AuthConnection = DDP.connect( AuthURL );
if ( AuthConnection ) {
console.log( router );
AuthConnection.call('logins.user', router, ( error, response )=> {
if ( error ) {
console.log( error );
} console.log( response );
} );
}
});
The logged in user is always present until the redirection that it becomes null. What do I do to maintain the logged in state of the user in the auth app?
I assume both apps connect to the same database?
When you redirect your local state changes - specifically in this case your local storage state which tracks user resume tokens.
If you want to allow one app to authenticate for another you need some form of SSO - a trivial implementation would be after login to request a resumeToken from the server, pass that to your second app in the URL, then use Meteor.loginWithToken. A more hacky (but maybe simpler?) way might be to copy the token saved in localStorage from app1, pass it in the URL to app2 then use save it there too.

How to ensure Anonymous login with Firebase?

I'm working on a React Native application which use Firebase Auth uid to identify individual user instances.
My initial implementation called signInAnonymously on every starting up and it returned non-persistent uid.
Referring to Anonymous user in Firebase, my code became like this. it works as expected but still unclear why this code is correct.
static ensureLogin() {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
resolve(user)
} else {
firebase.auth().signInAnonymously()
.catch(function(error) {
reject()
})
}
})
}
To answer this question, let's check details in this official document.
https://firebase.google.com/docs/auth/web/manage-users
It has this example code:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
The latter comment line says "No user is signed in." instead of "User is signed out.".
It implies that it is triggered not only by user's sign in/out actions.
It also says below.
By using an observer, you ensure that the Auth object isn't in an
intermediate state—such as initialization—when you get the current
user.
This means that 2 facts.
Auth object has internal states
onAuthStateChanged observes the state of Auth object
So, we should notice that onAuthStateChanged can be triggered by any status change of Auth object including initialization! And calling signInAnonymously after init is nothing strange.
Once signInAnonymously in else block is called, it re-triggers onAuthStateChanged after signing in.
That's why the code is correct.

Firebase Simple Login - Can I watch event when user remove his account?

In my app any user (signed up via Firebase Simple Login) can has 1 of 3 possible statuses at a time, that is one of active, deleted and suspended. I kept it like this:
"users": {
"simplelogin:1": {
"username": "user1",
"email": "user1#example.com",
"status": "active"
},
// ...
}
Any activity that involves any user will check his/her status, if it is deleted or suspended the action cannot be done.
Normally when user choose to remove his account from my app it would be like this:
var ref = new Firebase("https://my-app.firebaseio.com");
ref.child('users/simplelogin:1/status').set('deleted', function (error) {
if (!error) {
ref.removeUser({
email: "user1#example.com",
password: "secretpassword"
});
}
});
The problem is that user can remove his/her account without updating status to deleted first. For example this can be done by the user on his own browser console or on Node.js:
var ref = new Firebase("https://my-app.firebaseio.com");
ref.removeUser({
email: "user1#example.com",
password: "secretpassword"
});
This way the user will not has his account on Firebase but the status of this user that I kept above will remain active, and so my app will allow any activity for that user (such as someone sending him a gift, which shouldn't be allowed if his status has updated to deleted correctly.) That could be a problem. How can I update the status to deleted when user remove his/her account?
The solution I thought of is to regularly run cron job to check all the users in Firebase, try to sign up for users that does not has status deleted with random password and check if receive EMAIL_TAKEN error. If it can sign up, that means the user has already removed his/her account but the status in Firebase hasn't updated to deleted. In this case it has to update status to something (such as suspicious or deleted) and remove this newly signed up account again. I think this might be implemented like this:
// Assume this ref authenticated with admin privilege.
ref.child('users').on('child_added', function (snap) {
var user = snap.val();
if (user.status !== 'deleted') {
// Assume _generateRandomPassword gives generated random password.
var password = _generateRandomPassword();
var credentials = {
email: user.email,
password: password
};
ref.createUser(credentials, function (error) {
if (!error) {
snap.ref().child('status').set('deleted');
ref.removeUser(credentials);
}
});
}
});
I think this might not be a good solution since the system has to run for every user (that does not has deleted status), e.g. every night. Is there a better solution to this problem?

Resources