Angularfire Auth session persistence - angularjs

This is my first time using AngularJs, not sure whether my authentication handle correctly or not.
I expect $firebaseAuth() will keep the authentication status even if I refresh the app. But every time I refresh the app, $firebaseAuth() will not re-authenticate the user, and I need to re-login.
I read through the document and search in the source code, but can't find the function that allows me to configure the session persistence. Only 2 parameters email and password are accepted.
versions:
angularjs v1.4.10
angularfire v2.0.1
firebase v3.0.3
my authenticationService litcoffee script
app.factory "authenticationService", [
"$firebaseAuth"
"$location"
"$rootScope"
($firebaseAuth, $location, $rootScope) ->
login: (email, password, redirectUrl = "/") ->
$rootScope.authObj.$signInWithEmailAndPassword(email, password)
.then(
(firebaseUser) ->
console.log firebaseUser
$rootScope.firebaseUser = firebaseUser
return true
(error) ->
$rootScope.firebaseUser = null
console.log error
alert error.message
return false
)
logout: (redirectUrl = "/login") ->
$rootScope.authObj.$signOut()
$location.path redirectUrl
isLogged: () ->
if $rootScope.authObj.$getAuth()
return true
else
return false
]
I check user authentication status on a controller which will call authenticationService.isLogged(), it will redirect to login page if user is not logged.
What I want to achieve is just a simple authentication, user will remain authentication status even if they refresh.
Please correct my if I'm on the wrong direction. Any help would be greatly appreciated.

It's my fault actually.
Firebase and AngularFire keep the authentication session persistence by default.
What I did wrong is check the authentication status immediately when the page loaded. At the moment AngularFire hasn't fired yet, it will take some time to re-authenticate the application.
Thus, $firebaseAuth.$onAuthStateChanged can be very helpful.
Listen to it when your page loaded. Once the event fired, it will notify you whether the user authenticated.

Related

Auto-Logout after failed checksession using Identity Server 4, VueJS, and oidc-client

I'm testing both server and client on my machine and I'm experiencing the following: I log in to the client fine, do some work, close the browser without logging out. Then I open the client again and I am still logged in (expected) but then a minute later I am auto logged out (NOT-expected).
I am using the oidc-client.js configured like this:
var mgr = new Oidc.UserManager({
userStore: new Oidc.WebStorageStateStore({ store: window.localStorage }),
authority: 'http://localhost:5000',
client_id: 'TST_PORTAL',
redirect_uri: window.location.origin + '/static/callback.html',
response_type: 'code id_token token',
scope: 'api47 openid profile read write offline_access active_dir email',
post_logout_redirect_uri: window.location.origin + '/',
silent_redirect_uri: window.location.origin + '/static/silent-renew.html',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true
})
After further investigation I see the client is calling /connect/checksession (returns status 200) to support single sign-out and then calls /connect/authorize?client_id... which fails (302 redirects to /home/error). The identity server logs say "no user present in authorize request" and "invalid grant type for client: implicit. I have hybrid and client_credentials configured. I read something here so I added this code to my IdentityServer startup:
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
But this did not seem to help.
Thinking out loud, could this be a cross domain issue since these run on different ports or I don't have CORs correctly setup? I don't see cors errors. Also should the checksession GET request have parameters? I've been reading the spec and it talks about iframes but not the network traffic so I'm not sure what this traffic should look like.
Update:
The first page of my app is an anonymous auth landing page which checks if they are logged in. If so, it redirects them to the home page. The code for checking is this:
// Get signed in status without prompting to log in
getIsSignedIn() {
console.log('Checking if signed in');
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
console.log('Not Signed In');
return resolve(false)
} else {
if (user.expired) {
console.log('User expired');
return resolve(false)
} else {
console.log('Signed In');
return resolve(true)
}
}
}).catch(function (err) {
console.log('Error when checking if signed in');
return reject(false)
});
})
}
This seems to be returning true even when I open a fresh browser. I even changed the Oidc.WebStorageStateStore to use the default rather than localStorage.
There are a lot of things, that could cause this behavior.
1) Single-signout (monitorSession: true)
What does this do? -- After you login into your application (post redirect from IDP server, OIDC-Client JS will include the CheckSession endpoint in an iframe and OIDC library internally pings this iframe every 2 seconds (default) to verify that the idsrv.session cookie value matches with the value inside the applications id token, If they do not match, OIDC will raise user signed out event addUserSignedOut. It is up to your application, how you want to handle when this even get raised from OIDC. Just because you enable single sign out, unless application handles, it will not take the user back to login page.
2) Silent-Renew (automaticSilentRenew: true)
When you have this flag enabled to true, you don't have control on when the silent renew is supposed to happen, by default, it happens before 1 min of access token expiration time. So if silentrenew fails, will raise silentrenew event addSilentRenewError. It is up to your application, how you want to handle when this even get raised from OIDC.
3) From what you are saying if you close the entire browser, and go to url application URL, Identity server cookies should be deleted, as they are session cookies. So, if you had handled single signout event, then OIDC will raise the signout event within 2 seconds after the application gets loaded. Since you are not seeing the login page, I assume that you might not have handled this event in your application.
4) When the silent renew happens (before 1 min of token exp time), This time your app will communicate to IDP server, but you'll not have session cookies as you closed the browser and you will get silentrenew error (probably this time, you might have seen the error you described) and You must have handled silentrenew error in this case and that is why you are seeing login screen. (Just a wild assumption based on ur input).
Hope this helps !!

Prevent session sharing between browser tabs

I have a nasty bug: open two tabs with login pages and log in with different users in each one. All requests from first tab that logged in return with 'unauthorized' error.
Frontend uses SESSION cookie and it looks like that cookie is overwritten by second successful login of the second tab and it tries to use this new cookie when browsing in the first tab.
Using Spring Boot 1.5.8, Spring-session 2, AngularJS 1.7.2
Configuration is very standard, so I don't think these boilerplates would be useful.
Until now I tried to set up a filter on backend that works before authentication, to somehow filter out requests that have known cookie, but I failed at this.
UPD:
Some way of preventing that situation when a user is logged in but with incorrect session is what I seek. Either blocking second login attempt in this browser, or kicking already logged in user when another one logs in in the same broser - all will do.
You can log out user from other tabs if you set some sort of token on local storage like the code below(in sucessfull login response from server )
localStorage.setItem('logout', 'logout-' + Math.random());
and have this function as a run block in your main app module:
function logoutFromOtherTabs(authService, $timeout) {
'ngInject';
window.addEventListener('storage', function (event) {
if (event.key === 'logout') {
$timeout(function () {
authService.logout();
}, 1000);
}
});

Firebase 3. authentication doesn't persist

I'm working with firebase in a angularJS app, using the email and password authentication.
I just update my app from Firebase 2.x to 3.x and AngularFire from 1.x to 2.x.
I followed these 2 docs to do the migration :
https://firebase.google.com/support/guides/firebase-web
https://github.com/firebase/angularfire/blob/master/docs/migration/1XX-to-2XX.md
But now, each time I refresh my page, I need to re-authenticate, like there is no persistent session.
I've checked the localStorage key firebase:authUser I have an expirationTime in the past (actually it's set with the timestamp of my login).
To check if the user is loggedin I use : $firebaseAuth().$getAuth()
EDIT
Here is a working example of my problem (login: toto#mail.fr / password: toto123)
https://plnkr.co/edit/463Hse?p=preview
Does anybody know why this behavior ?
I am gonna guess that you are checking the currentUser directly without waiting for the initial auth state to resolve. You need to add an observer:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
https://firebase.google.com/docs/auth/web/manage-users
Also, auth state is stored in web storage so make sure you have that enabled. (state will not persist in safari private mode browsing for example).

Firebase $onAuth has wrong authData after $authWithOAuthRedirect from Facebook

I am trying to authenticate users of my Firebase (Angularfire) app with Facebook Login.
Everything works as expected when I authenticate with a pop-up window, but to support as many browsers as possible (Chrome on iOS doesn't support pop-ups, for e.g.) I want to fallback to authenticating with a redirect ($authWithOAuthRedirect).
I have confirmed my setting in Facebook are correct (my app ID and secret, for e.g.) but when I am redirected back to my app after Facebook authenticating with a redirect, $onAuth fires but I don't have my Facebook authData.
Instead, I have anonymous authData. For a bit of background; all users are authenticated anonymously if they are not otherwise authenticated (with Facebook, in this e.g.).
I can't see to find why this would be - the user should now be authenticated with Facebook, and have the Facebook authData.
Excepts of my code are below for some context:
Triggered when a user clicks the login button
function logIn () {
firebaseAuth
.$authWithOAuthRedirect('facebook', function (error) {
if (error) {
console.log(error);
}
});
}
$onAuth (inside my Angular app's run)
function run ($rootScope, firebaseAuth, sessionStore) {
$rootScope
.$on('$routeChangeError', function (event, next, prev, error) {
if (error === 'AUTH_REQUIRED') {
console.log(error);
}
});
$rootScope
.$on('$routeChangeSuccess', function (event, current, prev) {
$rootScope.title = current.$$route.title;
});
firebaseAuth
.$onAuth(onAuth);
function onAuth (authData) {
console.log(authData);
}
}
Route resolver to otherwise anonymously authenticates users
function sessionState ($q, firebaseAuth) {
var deferred = $q.defer();
firebaseAuth
.$requireAuth()
.then(deferred.resolve, guest);
return deferred.promise;
function guest () {
firebaseAuth
.$authAnonymously()
.then(deferred.resolve, rejected);
}
function rejected () {
deferred.reject('AUTH_REQUIRED');
}
}
The route resolver (sessionState) checks to see if the user is authenticated already, and if not, tries to anonymously authenticate them.
After the Facebook authentication redirect, the user will already be authenticated, and therefore does not need to be anonymously authenticated.
But, it appears that they are? As $onAuth logs the authData to the console, and it is anonymous.
Any help with this would be much appreciated! I am sure it has something to do with my route resolver, as pop-up authentication works fine (the route is already resolved).
EDIT: I tried completely removing my route resolver in case it was that causing an issue, but it made no difference. The user was just 'unauthenticated' instead of being either authenticated with Facebook (after $authWithOAuthRedirect) or anonymously.
UPDATE: I tried authenticating with Twitter and the redirect transport and I have encountered the exact same problem. I have also tried using port 80, instead of port 3000 that my app was being served on locally, but no joy.
UPDATE: When I turn off html5Mode mode in my app - and routes now begin with #s - $authWithOAuthRedirect works perfectly. From this I can only assume that $authWithOAuthRedirect does not support AngularJS's html5Mode. Can anyone confirm this is an issue, or do I need to change my code to support html5Mode and authWithOAuthRedirect?
EXAMPLE REPO Here is an example repo demonstrating the problem: https://github.com/jonathonoates/myapp
Look in the dist directory - you should be able to download this and run the app to reproduce the problem. In scripts/main.js is the app's JS; I've added a couple of comments but it's pretty self explanatory.
To reproduce the problem: click on the 'Facebook Login' button, and you'll be redirected to Facebook to authenticate. FB will redirect you back to the app, but here lies the problem - you won't be authenticated, and the returned authData will be null - you'll see this in the console
UPDATE: When I add a hashPrefix in html5Mode e.g.
$locationProvider
.html5Mode(true)
.hashPrefix('!');
The app works as I would expect - authenticating with Facebook and the redirect transport works.
Couple of niggles though:
The URL has #%3F appended to it, and is available/visible in the browser's history.
This would rewrite URLs with #! in browsers that do not support History.pushState (html5Mode), and some less advanced search engines might look for a HTML fragment because of the 'hashbang'.
I'll look into highjacking the URL upon being redirected back from Facebook instead of using hashPrefix. In the URL there is a __firebase_request_key which may be significant e.g.
http://localhost:3000/#%3F&__firebase_request_key=
It looks like this is indeed an incompatibility between Firebase and AngularJS's html5mode as you suspected. At the end of the redirect flow, Firebase was leaving the URL as "http://.../#?", and Angular apparently doesn't like that so it did a redirect to "http://.../" This redirect interrupts Firebase (the page reloads while we're trying to auth against the backend) and so it is unable to complete the authentication process.
I've made an experimental fix that ensures we revert the URL to http://.../#" at the end of the redirect flow, which Angular is happy with, thus preventing the problematic redirect. You can grab it here if you like: https://mike-shared.firebaseapp.com/firebase.js
I'll make sure this fix gets into the next version of the JS client. You can keep an eye on our changelog to see when it is released.

how to handle passport-facebook callback in angular client?

I am developing a MEAN application. I am using passport for authentication- local, facebook and google strategies.
I am using angularjs client. All the routing is handled at client. I am only consuming server data apis.
When using passport-facebook strategy, I am using below code at node server as per passport docs.
app.get('/auth/facebook',passport.authenticate('facebook-auth', { scope : ['email'] }));
app.get('/auth/facebook/callback',passport.authenticate('facebook-auth', {
successRedirect : '/home',
failureRedirect : '/login',
scope:['email']
}));
Problem I am facing is when user click on "Sign in using Facebook" button
<i class="fa fa-facebook"></i> Sign in using Facebook
Client will access "/auth/facebook" route that will eventually redirect user to facebook page for validating user's credentials.
After successful validation, user will be redirected to route "/home" as defined in "successRedirect" value.
Now the thing is, I want to use custom callback function instead of defining redirects for success or failure. It will look like below:
app.get('/auth/facebook/callback',passport.authenticate('facebook-auth', function(err,user,info){
if(err){
throw err;
}
else if(user === 'userexists'){
res.json({
'state':false,
'message':'User with this e-mail already exists'
});
}
else{
req.logIn(user,function(loginErr){
if(loginErr){
throw loginErr;
}
res.json({
'state':true,
'message':'You logged in successfully!'
});
});
}
}));
The root problem I am facing here, I can not use above custom callback as Client is not calling the "auth/facebook/callback" route, it is called by facebook.
So, there is no success handler waiting to catch above callback's response at client side!!
I want some way to get response in json form at client to eliminate server side redirection and also way to pass message and username to client after successful authentication by facebook.
I am about to give up with passport. Hoping for any possible solution before removing a lot of code!
Thanks
This can be accomplished by redirecting to another endpoint inside the facebook callback handler. There is no need to do res.json() on the callback from facebook since they only make a request to that in order to let you know if auth failed or succeeded. From their docs:
// GET /auth/facebook/callback
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
So facebook returns control over request process back to you when they call /auth/fb/callback but it's up to you what to do next. Since once the user is successfully authenticated, you would have req.user available throughout the whole session. At this point, you can redirect to something like the have in the example /account and check if req.user with req.isAuthenticated() and complete the flow you desire.

Resources