Firebase - SPA web application - issue with logout - javascript

I've recently made simple SPA application which connects to firebase using Google provider and loads data for authenticated user.
Everything was great, until I tried to sign-out user using following method from documentation:
firebase.auth().signOut()
Logout was succesful, but after this, I can't sign-in again, because I'm receiving following error:
updateCurrentUser failed: First argument "user" must be an instance of Firebase User or null.
When I checked network tab in my browser, I've seen my user data in responses, so there Was an issue propably with the firebasewebui.
Things which I also tried
Sign-in in another browser - working
Sign-in in incognito mode - not working
Sign-in from other domain (for instance fake domain authorized in firebase console) - working
Wiped my entire Google Chrome profile from computer and add it again - not working
Sign-in from Android application - working (here there is no issue with sign-out and sign-in)
So it looks like it is something connected with domain and browser combination.
Here is my js code:
const firebase = require('firebase/app');
require('firebase/auth');
require('firebaseui');
const initializeFirebase = () => {
const config = { /* config */ };
firebase.initializeApp(config);
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
// loads data
} else {
// visibility staff
initializeFirebaseAuthForm();
}
});
}
const initializeFirebaseAuthForm = () => {
const uiConfig = {
callbacks: {
signInSuccessWithAuthResult: function (authResult, redirectUrl) {
return false;
},
uiShown: function () {
visibilityManager(false);
}
},
signInFlow: 'popup',
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID
]
};
let ui = null;
if (firebaseui.auth.AuthUI.getInstance()) {
ui = firebaseui.auth.AuthUI.getInstance();
} else {
ui = new firebaseui.auth.AuthUI(firebase.auth());
}
ui.start('#firebaseui-auth-container', uiConfig);
}
document.addEventListener('DOMContentLoaded', function () {
initializeFirebase();
});
In such case, my observer registered in onAuthStateChanged is not fired.

I've found answer by myself.
There was couple of issues. First of all, Firebase initialization should be placed as 'global' like for example here: https://github.com/firebase/firebaseui-web/, especially this lines:
firebase.initializeApp(config);
ui = new firebaseui.auth.AuthUI(firebase.auth());
Secondly, with my code from input, I've to get existing instance of ui using conditional. In firebase github, ui was created always using new operator and it was always created once per script run.
I found out, that there is workaround - ui instance can be deleted using delete() promise.

Related

VueJS + Firebase Authentication Can't Access Session Before Scripts Load

So i'm working on a project and i'm struggling a bit...
I'm using VueJS for the frontend and Firebase Authentication to log users in.
I'm trying to determine whether a user is currently logged in or not using firebase.auth().currentUser;
(If returns null then no user is logged In) Sounds simple right!?
I'm running the currentUser() in my vueJS created() function. However, running firebase.auth().currentUser; here seems to always return null. But however, if i use a set timeout method then it is able to fetch the users data. It looks to me like vue is trying to fetch the data before it has loaded in.
I hope this isn't too hard to understand - i'm quite new to Vue and firebase!
Please find snippets of my code attached below.
Vue.config.devtools = true;
var app = new Vue({
el: '#app',
data: {
user: '',
loggedIn: false
},
created() {
var user = firebase.auth().currentUser;
if (user != null){
this.user = user;
this.loggedIn = true;
} else {
this.loggedIn = false;
}
}
})
Below are the firebase scripts i'm loading at the bottom of the page body
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-analytics.js"></script>
<script src="js/firebaseConfig.js" type="text/javascript"></script>
Essentially, how can i fix this without using a setTimeout() method? and also how does firebase get this session, which script handles this?
firebase.auth().currentUser always returns null when the page is first loaded. It will only contain an actual user object after the SDK has determined that the user is actually signed in. There is no guarantee exactly how long that will take.
The preferred way to find out when that happens is to use an auth state observer, as shown in the documentation.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
// ...
} else {
// User is signed out.
// ...
}
});
You can use this callback to determine when it's time to render content for that individual user.
I suggest also reading more about the behavior of the Firebase Auth SDK in this blog.
async created() {
var user = await firebase.auth().currentUser;
if (user != null){
this.user = user;
this.loggedIn = true;
} else {
this.loggedIn = false;
}
}

Silent authentication for own website inside tab of custom Teams app

After two months of experimenting with Teams Authentication via adal.js and msal.js and failure, I’m close to giving up. So I really need your help.
Basically I need to “silently” authenticate the logged in Teams User for my own website (tab) inside my app that I created with App Studio. The reason for that is, so that I can use the data of the authentication token for the login of my own website.
So far I was only able to get this working with msal.js and a popup, which according to Teams developer I’ve asked is not the way to go. Understandable, since I cannot use the popup method on the Teams Client because it gets blocked.
I’ve tried this silent login method (https://github.com/OfficeDev/microsoft-teams-sample-complete-node/blob/master/src/views/tab-auth/silent.hbs) that was recommend to me.
Sadly it didn’t work. All I get is a “Renewal failed: Token renewal operation failed due to timeout” error.
Since the msal.js popup variant (Node.js Azure Quick Start Example) I used before worked in a web browser, I don’t think that the configuration of Azure App is wrong.
This is my code so far:
// onLoad="prepareForm()"
<!--- Import package for authentication information in Teams/Azure--->
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.15/js/adal.min.js" integrity="sha384-lIk8T3uMxKqXQVVfFbiw0K/Nq+kt1P3NtGt/pNexiDby2rKU6xnDY8p16gIwKqgI" crossorigin="anonymous"></script>
<script src="https://statics.teams.microsoft.com/sdk/v1.4.2/js/MicrosoftTeams.min.js" crossorigin="anonymous"></script>
<script language="JavaScript">
let config = {
clientId: "1402f497-d6e8-6740-9412-e12def41c451", // I've changed it for this stackoverflow post
redirectUri: "https://myredirect.com", // I've changed it for this stackoverflow post
cacheLocation: "localStorage",
navigateToLoginRequestUrl: false,
};
microsoftTeams.initialize()
/// START Functions for Teams
function getTeamsContext() {
microsoftTeams.getContext(function(context) {
startAuthentication(context);
});
}
function startAuthentication(teamsContext) {
if (teamsContext.loginHint) {
config.extraQueryParameters = "scope=openid+profile&login_hint=" + encodeURIComponent(teamsContext.loginHint);
} else {
config.extraQueryParameters = "scope=openid+profile";
}
let authContext = new AuthenticationContext(config);
user = authContext.getCachedUser();
if (user) {
if (user.profile.oid !== teamsContext.userObjectId) {
authContext.clearCache();
}
}
let token = authContext.getCachedToken(config.clientId);
if (token) {
console.log(token)
// Get content of token
} else {
// No token, or token is expired
authContext._renewIdToken(function (err, idToken) {
if (err) {
console.log("Renewal failed: " + err);
// Some way of logging in via Popup or similiar
} else {
console.log(idToken)
// Get content of token
}
});
}
}
/// END Functions for Teams
// initialized on page load!
function prepareForm() {
getTeamsContext();
document.InputForm.password.focus()
}
<script/>
Those are my questions:
What causes this error?
How do I authenticate the token on manipulation and is it Teams or Azure? (Does adal.js any functions for this?)
How do I login if the silent authentication fails and popups are blocked? Is there a website for authentication provided by Teams that returns a token?
Are there any working examples of the silent authentication that are not from the official Microsoft website? (I don't understand them.)

Page visibility event not stability fired in Vue PWA

I'm building a Vue PWA with Firebase authentication. The web app will listens Firebase's onAuthStateChanged event on App first loaded to automatically sign the user in and save his ID token for API requests latter, by invoke Firebase's getIdToken(/* forceRefresh */ true).
Beside that, I also utilize Page Visibility API to reload the Web App after 5 minutes hidden (to get new Firebase ID token if the old one has expired).
On my Android phone, I visit my web app URL on Chrome, then add icon to home screen and make all test cases by access the web app thru that icon.
Here is the test case: after sign in and using the web app normally, I click Home button to hide the web app, then after ~10 minutes, I recall the app from background state, the web app was auto-reload successfully then I could continue using it as normal. The problem is, if I recall the app from background after a long time (~6 hours), the web app do not auto-reload then I don't have new Firebase ID Token of the user, as a result I get Token Expired error when making API request to get user profile...
I need to findout a reliable way to trigger autoLogin() function, so users don't need to re-login every time when they come back using my WebApp.
Here are skeleton code base:
main.js
const unsubscribe = fibAuth.onAuthStateChanged((user) => {
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App },
created () {
// Firebase auto login
if (user) {
store.dispatch('autoLogin', user)
}
// Reload after a duration
document.addEventListener('visibilitychange', function () {
store.dispatch('appVisibilityHandler', document.visibilityState)
})
} // end created()
}) // end Vue()
unsubscribe()
})
Vue Store - index.js
async autoLogin ({commit, state, dispatch}, userPayload) {
commit('SET_APP_LOADING', true)
try {
let idToken = await userPayload.getIdToken(/* forceRefresh */ true)
console.warn('store::autoLogin() - idToken:', idToken)
let apiResponse = await UsersRepos.getMyProfile(idToken)
// ... processing apiResponse ...
} catch (error) {
console.error('store::autoLogin() - error:', error)
}
commit('SET_APP_LOADING', false)
},
appVisibilityHandler (context, statePayload) {
try {
const APP_REFRESH_SECONDS_THRESHOLD = 300 // 5 minutes
if (statePayload === 'hidden') {
localStorage.setItem('app-hidden-ts', (new Date()).getTime())
} else if (statePayload === 'visible') {
let lastSec = parseInt(localStorage.getItem('app-hidden-ts') / 1000)
let nowSec = parseInt((new Date()).getTime() / 1000)
localStorage.setItem('app-hidden-ts', nowSec * 1000)
console.warn('total hidden seconds:', (nowSec - lastSec))
if (nowSec - lastSec > APP_REFRESH_SECONDS_THRESHOLD) {
context.commit('SET_APP_LOADING', true)
// refresh the whole web page
router.go()
}
}
} catch (error) {
alert('appVisibilityHandler error:' + error.message)
}
}
I really appreciate any guide or clue to overcome the issue. Thank you in advance!
Firebase Authentication uses ID tokens that are valid for an hour. Calls to getIdToken() return this token. The SDK automatically refreshes the ID token in the background, but of course can't recall your autoLogin since the authentication state didn't change.
You'll want to attach an onIdTokenChanged handler to detect when the ID token has changed and pick it up.
firebase.auth().onIdTokenChanged(function(user) {
if (user) {
// User is signed in or token was refreshed.
store.dispatch('autoLogin', user)
}
});
In fact, this might replace your onAuthStateChanged handler, since this also fires when the user signs in.

Stay logged in when using msal.js

I'm building a small JS app for my Microsoft ToDo tasks and use the msal.js library to accommodate the authentication process.
This works perfectly fine, I get a popup, I authenticate my profile, the popup closes and my tasks appear on my screen.
But: It doesn't seem to remember that I authenticated before; Every time I run my webpack app and the page is booted it shows the popup and asks for authentication. Once I've authenticated and just refresh my page, it just shows me the tasks without showing the popup again. I haven't tried waiting for an hour but I think it has something to do with not properly refreshing my access token. I'm not that involved with the Outlook/Microsoft API that I can really figure out if I'm using it correctly.
In short: How can I authenticate once so that the next time I start my app the tasks are shown without having to authenticate again?
My init function
this.userAgentApplication = new Msal.UserAgentApplication(microsoftTasksClientId, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
console.log(token)
})
let user = this.userAgentApplication.getUser()
if (!user) {
const self = this
// this.userAgentApplication = new Msal.UserAgentApplication(microsoftTasksClientId)
this.userAgentApplication.loginPopup([`${this.apiRootUrl}Tasks.readwrite`]).then(function (token) {
self.idToken = token
user = self.userAgentApplication.getUser()
if (user) {
self.getSilentToken()
}
}, function (error) {
console.log(error)
})
} else {
this.getSilentToken()
}
And my getSilentToken function
const self = this
this.userAgentApplication.acquireTokenSilent([`${this.apiRootUrl}Tasks.readwrite`]).then(function (token) {
console.log('ATS promise resolved', token)
self.accessToken = token
self.getTasks()
}, function (err) {
console.log(err)
})
Please not that my code isn't refactored AT ALL! ;-)
What version of MSAL are you using?
There is a problem in 0.1.1 version that storage is 'sessionStorage' by default and can't be really changed. In that case your login is saved just for currently opened window and you will be forced to relogin even when opened new browser window.
You should use 'localStorage' to achieve what you want and pass it as a constructor parameter for UserAgentApplication.
Here is a fix in their repo for this:
https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/eba99927ce6c6d24943db90dfebc62b948355f19

Getting id of the current user from Cloud Functions and Firebase

I am using google cloud functions to register push notifications through firebase. In my app, i have a notifications reference that changes for a current user whenever they get a new follower or like, etc. As of right now I am able to send the notification to the phone whenever that whole reference child changes
For example, if any single post is liked, then it will send a notification. What I need to do is observe the current user to only send the notification that single person.
Here is my JavaScript file
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendPushNotification = functions.database.ref('/notification/{id}').onWrite(event => {
const payload = {
notification: {
title: 'New message arrived',
body: 'come check it',
badge: '1',
sound: 'default',
}
};
return admin.database().ref('fcmToken').once('value').then(allToken => {
if (allToken.val()) {
const token = Object.keys(allToken.val());
return admin.messaging().sendToDevice(token, payload).then(response => {
});
}
});
});
I would like to replace this line:
functions.database.ref('/notification/{id}').onWrite(event => {
With this:
functions.database.ref('/notification/{id}').(The current user ID).onWrite(event => {
How do I get the current users id?
You seem very new to JavaScript (calling it JSON is sort-of a give-away for that). Cloud Functions for Firebase is not the best way to learn JavaScript. I recommend first reading the Firebase documentation for Web developers and/or taking the Firebase codelab for Web developer. They cover many basic JavaScript, Web and Firebase interactions. After those you'll be much better equipped to write code for Cloud Functions too.
Now back to your question: there is no concept of a "current user" in Cloud Functions. Your JavaScript code runs on a server, and all users can trigger the code by writing to the database.
You can figure out what user triggered the function, but that too isn't what you want here. The user who triggered the notification is not the one who needs to receive the message. What you want instead is to read the user who is the target of the notification.
One way to do this is to read it from the database path that triggered the function. If you keep the notifications per user in the database like this:
user_notifications
$uid
notification1: ...
notification2: ...
You can trigger the Cloud Function like this:
exports.sendPushNotification = functions.database.ref('/user_notification/{uid}/{id}').onWrite(event => {
And then in the code of that function, get the UID of the user with:
var uid = event.params.uid;
For Swift 3.0 - 4.0
You can do this:
import Firebase
import FirebaseAuth
class YourClass {
let user = Auth.auth().currentUser
let userID = user.uid
// user userID anywhere
}

Categories