Firebase Authenticatoin - onAuthStateChanged called multiple times - javascript

Whenever I render the home page or account page my session is sometimes lost. I have a function called isSignedIn() that checks if the uid exists. Sometimes, when I refresh the homepage, it returns false. When I hit my account endpoint it returns false too even though it's true on the previous page. Any ideas on why this is happening?
How my app works:
When a user logs in I take the uid from client side and hit an auth endpoint.
function createServerSession(user) {
// Get secret token from Firebase for current session
firebase.auth().currentUser.getToken(true)
.then(function(idToken) {
// Get session on my Node server for server user identification
if (idToken) {
fetch('/firebaseauth?idtoken=' + encodeURIComponent(idToken) + '&username=' + encodeURIComponent(user.email.split('#')[0]), {
credentials:'include'
}).then(function(data){
if (window.location.pathname === '/' && document.getElementById('login').innerHTML.trim() === 'Join') {
redirect('/');
}
if (window.location.href.includes('login')) {
redirect('/');
}
})
}
})
}
Then it hits my node.js code to store the session:
app.get('/firebaseauth', function (req, res) {
if (! req.session.idToken) {
firebase.auth().verifyIdToken(req.query.idtoken).then(function(decodedToken) {
req.session.username = req.query.username
req.session.idToken = decodedToken.uid;
req.session.save(function(){
res.json(true)
})
})
This approach works fine most of the time, but some instances it will send multiple requests. The highest amount being 7400 at one point. Any guidance is appreciated.

Since posting this issue, I've switched hosts from GAE to Heroku and that fixed it. I know the functionality with node.js is in beta so maybe that was it

Related

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

Creating a user session - NODE js

I am new to node js & javascript in general. I have the below piece of code that will handle a login. I have a MYSQL database with a customer table. When the customer enters their username and password, it checks does it exist in the database. This part is working.
I now want to enhance this feature so that it will take the username and create some sort of a session variable, which can be used across the application. I am new to JS so I am not yet sure which inbuilt facilities already exist, or best practice around sessions.
I want to be able to use this session variable across the application, and for subsequent logout facility.
Can someone advise me on this, or point me in the right direction? Thanks.
case "/login":
var body = '';
console.log("user Login ");
request.on('data', function (data) {
body += data;
});
request.on('end', function () {
var obj = JSON.parse(body);
console.log(JSON.stringify(obj, null, 2));
var query = "SELECT * FROM Customer where name='"+obj.name+"'";
response.writeHead(200, {
'Access-Control-Allow-Origin': '*'
});
db.query(
query,
[],
function(err, rows) {
if (err) {
response.end('{"error": "1"}');
throw err;
}
if (rows!=null && rows.length>0) {
console.log(" user in database" );
theuserid = rows[0].customerID;
var obj = {
id: theuserid
}
response.end(JSON.stringify(obj));
}
else{
response.end('{"error": "1"}');
console.log(" user not in database");
}
}
);
});
}
There can be multiple ways of implementing a user session.
One, you could use a browser cookie, it comes with many pros and cons and you should read about it a bit to see how its managed. This would also depend on the server you are using (express, hapi, etc).
Two, you can set a JWT token on the backend, and include it in the header of the response, then you can either use your application state or the local storage of the browser to save that token on the UI. Any such follow up requests requiring authentication should contain this auth token as a header for verification.
For more clarity, you can look into related libraries (such as passport), which make this task a lot easier.
PS: If you choose cookies, please make sure the business is going to allow it or not as the end-users do not like being tracked always. :)

How to logout from another browser after reset the password

I want to logout from another browser after reset the password.
I use passport.js for user authentication.
So i need a such kind of functionality that if i change my password from one browser then it will automatically logout that user from another browser(When switch to another browser).
Any idea?
function validateToken(token) {
return require("mongoclient").then(function (DB) {
return DB.query({$collection: "pl.connections", $filter: {token: token}});
}).then(function (data) {
if (data && data.result && data.result.length > 0) {
return true;
} else {
return false;
}
})
}
not a complete implementation but a sample code to validate the token in node with mongo
Add A bit of functionality in your app. maintain a table of token in your db corresponding to users so after a change to password just expire the tokens that are in table for a specific user. Its not just a idea its a working strategy that is adopted by many apps.
You can easily archive this using session Id which is generated each time a user is authenticated and is unique to a user. But you have to keep track of the Id
In this solution am using express-session
After a successful authentication req.session.id is assigned to the user, it is an alpha-numeric Id(6QP2t2_ffzkLNPHWNIEuRSXEvNm4lzLb). You can save this user session Id on account login, Then you can retrieve the user's session from the memory store at any time and destroy it:
let session = require('express-session');
const id = req.session.id
sessionStore = express.session.MemoryStore();
sessionStore.get(id, function(err, sess) {
sess.destroy (function (err) {
console.log(err, 'if any')
});
});
For example you can add expireAfter flag to your users table. And when you need to logout somebody just change their expireAfter to current time.
If current timestamp is greater than expireAfter then you just clear cookies in browser when request comes.
And when login just set expireAfter to 1st January of 2030
// something like that
var app = express();
app.use(function(req, res, next) {
var user = { ... } // load user from db
if (user.expireAfter >= new Date().getTime()) {
res.clearCookie('user_id') // or whatever you have
var err = new Error('not authorized, please re-login');
err.status = 403;
return next(err);
}
next();
});
... code code code ...
You can create timer at your client side.
use setInterval function.
Send function that will go to server and check if you need logout.
(This Logic is used to check expire sessions, at some systems)
UPDATE:
following to the comment, I notice that I forgot wrotten critical detail:
Of course, server itself need prevent any action when your password is changed. I meant to client side - that you want to logout it.

Categories