Login Persistance in Firebase (V9) - javascript

I have been reading the user documentation for browser login Persistence at Google Documentation. However its not clear enough, on what I should be doing for v9. Briefly here's what I understood. There are three types of persistence's: Per Session, Per Browser, and None. So now what I wanted to do was set Persistence for "per Browser". I saw two functions being used, one is inMemoryPersistence which I assumed to be per Browser and the other to be browserSessionPersistence which i assumed to be "per Session". However even after trying with both of them, after closing the tab and reopening its sends me to the login page.
const auth = getAuth();
setPersistence(auth, browserSessionPersistence)
.then(() => {
// Existing and future Auth states are now persisted in the current
// session only. Closing the window would clear any existing state even
// if a user forgets to sign out.
// ...
// New sign-in will be persisted with session persistence.
return signInWithEmailAndPassword(auth, email, password);
})
.catch((error) => {
// Handle Errors here.
const errorCode = error.code;
const errorMessage = error.message;
});
My code is just the same as what's provided in the docs, with browserSessionPersistance being one of two parameters I tried.
So my question is:
Is my understanding of the 3 Types Correct?
If it is, then why isn't browser persistence working?
(Also I'm using Vanilla Javascript with Modular Import)

If you want to persist auth state and required explicit logout, use browserLocalPersistence.
Version V8 firebase.auth.Auth
Version V9
Description
Persistence.LOCAL
browserLocalPersistence
Explit logout required
Persistence.SESSION
browserSessionPersistence
current session or tab, cleared when closed
Persistence.NONE
inMemoryPersistence
in memory, not persisted, cleared when page is refreshed
Also checkout Persistence interface in the documentation.

Related

Google oauth session lost after page reload (javascript)

I recently moved from the deprecated gapi.auth2 to the new Google Identity Services, using the javascript client library, and noticed a big difference: if someone signs in, and then reloads the page, the session is lost, and has to sign in again, every time the page is loaded. This was not the case with the deprecated library.
The problem can be easily reproduced with the Calendar API example.
Is there any configuration option to keep the session persistent? Or do I need to store the access tokens somehow? I could not find anything relevant in the official docs.
UPDATE:
The migration guide states the following:
Previously, Google Sign-In helped you to manage user signed-in status using:
Callback handlers for Monitoring the user's session state.
Listeners for events and changes to signed-in status for a user's Google Account.
You are responsible for managing sign-in state and user sessions to your web app.
However there's absolutely no information on what needs to be done.
UPDATE 2
To be more specific, the actual issue is not making the session persistent. Managing the sign in state and user session is something I can solve.
The real problem is the access token used to call the Google APIs.
As mentioned in the comments, the access tokens are 1) short lived 2) are not stored anywhere, so even if not expired, they do not persist between page reloads.
Google provides the requestAccessToken method for this, however even if I specify prompt: '', it opens the sign-in popup. If I also specify the hint option with the signed in user's email address, than the popup opens, displays a loading animation briefly, and closes without user interaction. I could live with this, however this only works if triggered by a user interaction, otherwise the browser blocks the popup window, meaning that I cannot renew the token without user interaction, e.g. on page load. Any tips to solve this?
I faced all the same issues you described in your question.
In order to help:
Google 3P Authorization JavaScript Library: in this link we can check all the methods the new library has (it does not refresh token, etc..)
This doc says the library won't control the cookies to keep the state anymore.
Solution
Firstly I need to thanks #Sam O'Riil answer.
As Sam described: "you can somehow save access token and use it to speed-up things after page reload."
Given the the Google's exampe, we should call initTokenClient in order to configure the Google Auth and the requestAccessToken to popup the auth:
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
prompt: 'consent',
callback: tokenCallback
});
tokenClient.requestAccessToken({prompt: ''})
In your tokenCallback you can save the credentials you get somehow, e.g.:
const tokenCallback(credentials) => {
// save here the credentials using localStorage or cookies or whatever you want to.
}
Finally, when you restart/reload your application and you initialize the gapi.server again, you only need to get the credentials again and set token to gapi, like:
gapi.load('client', function() {
gapi.client.init({}).then(function() {
let credentials = // get your credentials from where you saved it
credentials = JSON.parse(credentials); // parse it if you got it as string
gapi.client.setToken(credentials);
... continue you app ...
}).catch(function(err) {
// do catch...
});
});
Doing it, your application will work after the reload. I know it could not be the best solution, but seeing what you have and the library offers, I think that's you can do.
p.s.: the token expires after 1 hour and there is no refresh token (using the implicit flow) so, you will have to ask the user to sign-in again.

why firebase authentication is slow?

I am building a login page in my React app using firebase (sign in with google redirect the user method)
and it is working but it takes almost two seconds for firebase to get the current user, which is not the best for UX.
here is the code
componentDidMount(){
console.log(Date.now());
const auth = getAuth();
this.authListener = onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in
console.log(Date.now());
this.props.dispatch(addUserAction(user.email))
}
else {
// User is signed out
this.props.dispatch(addUserAction(null))
}
});
}
what i get in my console is that the difference of time is 1768 milliseconds which is almost 2 seconds,
am i doing something wrong ?
the console is showing the difference of time as 2 seconds
When you restart the app/reload the page, Firebase automatically restores the user's authentication state based on the information it stored in local storage when the user first signed in. For this it does make a call to the server though, to check whether the credentials are still valid - and for example to ensure the account hasn't been disabled. It's likely that this call is what is taking time in your use-case.
A common trick is to make your own determination on whether the server-check is likely to succeed based on only client-side information. For this, store an extra value in local storage when the user signs in successfully, say isAuthenticated. Now when you reload the page/app, you an read this value from local storage, and then the user was previously authenticated, assume that they will be authenticated again.
The assumption may be wrong of course, so you'll have to handle that scenario too in your code.
Also see this talk Architecting Mobile Web Apps, where Michael Bleigh talks about the technique.

Msal v2 library, handle logout for SSO (Node.js and Vue.js)

The problem:
Using msal v2, when user log in to the app via Microsoft account, it saves params to the sessionStorage and it all works great, problem happens when user logs out in the Office.com or any other site using Microsoft SSO. Since the data is still saved in sessionStorage (tried same with localStorage) the AcquireSilentToken(...) resolves with the cached data, even though the user has been logged out.
Tried How to know if a given user is already logged in with MSAL?
It suggest using AcquireSilentToken(...) but it resolves promise without error since it checks sessionStorage.
My case:
In the middleware I would like to do:
const promise = msalInstance.acquireTokenSilent(graphScopes);
promise.then(resp=>{
//User is logged continue next();
}).catch(error=>{
//User is not logged in clear sessionStorage/localStorage and next('/login')
});
So if anyone can help me with the way of asking the thru msal if user has logged out. I would really appreciate it.
This behavior is by design. AAD services uses cookies to remember who you are and to automatically sign you in.
The sign-out process for services forces the session cookies to expire. These session cookies are used to maintain your sign-in state when you use these services. However, because the web browser is still running and may not be updated to handle cookies correctly, you may have a cookie that is not updated to expire and finish the sign-out process. By default, these cookies are valid for eight hours or are set to expire when you close all web browsers.
const promise = msalInstance.acquireTokenSilent(graphScopes);
promise.then(resp=>{
const logoutRequest = {
account: instance.getAccountByHomeId(homeAccountId),
postLogoutRedirectUri: "your_app_logout_redirect_uri"
}
instance.logoutRedirect(logoutRequest);
}).catch(error=>{
//User is not logged in clear sessionStorage/localStorage and next('/login')
});
Also this is a known issue.

Why would you want to sign out after creating a session in Firebase?

I'm looking at the Firebase auth docs here and specifically this code example for the client-side code for creating a session:
firebase.auth().signInWithEmailAndPassword('user#example.com', 'password').then(user => {
// Get the user's ID token as it is needed to exchange for a session cookie.
return user.getIdToken().then(idToken = > {
// Session login endpoint is queried and the session cookie is set.
// CSRF protection should be taken into account.
// ...
const csrfToken = getCookie('csrfToken')
return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken);
});
}).then(() => {
// A page redirect would suffice as the persistence is set to NONE.
return firebase.auth().signOut();
}).then(() => {
window.location.assign('/profile');
});
The first section there makes sense-- sign in and create a session. But then the middle then calls signOut -- what? Why would you want to do that? There is a comment preceding this code in the docs that reads:
On success, the state should be cleared from the client side storage.
Unclear if that comment is referring to the signOut call. Not really sure why you would do that, either way....then firebase thinks the user is signed out, but your server has an active session for that user.
Could anyone shed any insight on this?
There is a line of code from that sample that's important for context:
// As httpOnly cookies are to be used, do not persist any state client side.
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
With persistence disabled, there is no saved sign-in state. When the page reloads, redirects, or somehow navigates away, the user is effectively signed out, because their token isn't remembered. The point of the entire sample is to show how to put that token into a cookie, which will be persisted as cookies normally are, and also sent to the server on future requests and can be verified with the Firebase Admin SDK. If this is not what you're trying to do, then this page of documentation isn't relevant.
The signOut that happens in later is merely ceremonial. As the comment above it says:
A page redirect would suffice as the persistence is set to NONE.
Signing out would be an explicit note to the reader of the code that the idea is to use the token stored in the cookie, not in Firebase Auth's own persistence (which, again, was disabled above).

Sometimes firebase.auth().onAuthStateChanged returns user of null when I refresh the page

Precondition
$npm install --save firebase#4.11.0
issue
I'm using firebase authentication on my web application.
In my app, I implemented onAuthStateChanged for client side js like below.
firebase.auth().onAuthStateChanged((user) => {
if(user) {
//logged in
} else {
//do sth
}
});
After login, I confirmed this method will return actual user obj, but if I refresh the page, then user might be null.
Curiously, sometimes user won't be null.
I'm afraid there are some limitation of calling onAuthStateChanged, but currently I have no idea.
How should I deal with this issue?
update
Let me share my minimal example.
My app is working with express.js.
There are two URLs like below.
/login
/main
In the login page, I implemented authentication method.
If the login is successfully finished, then user will be redirected to '/main'.
//login.js
import firebase from 'firebase';
var config = {...};
firebase.initializeApp(config);
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider)
.then((result) => {
return result.user.getIdToken(true);
}).then((idToken) => {
if(idToken) {
location.href = '/main';
}
});
In the main page, there is no login method.
main.js is only checking whether user is logged in.
//main.js
import firebase from 'firebase';
var config = {...};
firebase.initializeApp(config);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
//initialize main page.
} else {
location.href = '/login';
}
}
I think login status is stored on LocalStorage of web browser.
This means that, after finishing loading of main.js, onAuthStateChanged will be automatically fired with user information, but not working as I expected.
I'm sure that persistence of login information is correct because official document says the default setting is LOCAL for web client.
https://firebase.google.com/docs/auth/web/auth-state-persistence
my question
Should I implement onAuthStateChanged with another way?
How can I ensure user is logged in after reload?
e.g.
import $ from 'jquery';
$(document).on('ready', () => {
onAuthStateChanged((user) => {...});
});
Or could you show me the correct way?
Workaround
I decided to remove session and set redirection to login page if null is returned. This is not a solution, but a workaround currently...
You're not calling onAuthStateChanged. Instead you're telling Firebase to call you when the authentication state changes, which may happen a few times when the page is being re-loaded
When a page is getting loaded and there was previously a user signed in, the auth state may change a few times, while the client is figuring out if the user's authentication state it still valid. For that reason, you may see a call with no user before seeing the final call with the actual signed in user.
The fact it's sometimes null and sometimes not null likely points to an async problem. Are you making the check in the if statement above? All references to the user should be within the callback. If that all checks out, maybe check that authentication is being properly initiated.
onAuthStateChanged is an observer as stated in firebase docs, which gets triggered when the auth state is changed like user signed in, signed out, pwd change. To check if user is logged in or not you should use firebase.auth().currentUser which will give you the current logged in user. As you said your state is local firebase.auth().currentUser will always give you user unless user is signed out.

Categories

Resources