I'm trying to update my angularjs app to support Firebase 1.1 (I was stick with Firebase 1.0.x).
It deprecates firebasesimplelogin, including authentication inside Firebase core.
I have been able to successfully implement authentication using
authWithOAuthPopup("<provider>", function(error, authData) { ... });
It accepts a callback, which is passed authentication data in authData.
On the contrary, I can't undersand how to use
authWithOAuthRedirect("<provider>", function(error) { ... });
Firebase Authentication docs page is very concise... :-(. This is all what is said:
Alternatively [instead of authWithOAuthPopup], you may prompt the user to login with a full browser redirect, and Firebase will automatically restore the session when you return to the originating page
How do I get authData, when Firebase - after redirection - returns to my page?
The authData is available by registering a listener directly on the ref (so before calling authWithOAuthRedirect).
ref.onAuth(function(authData) {
...
}
ref.authWithOAuthRedirect("google", function(error) { ... });
See https://www.firebase.com/docs/web/guide/user-auth.html#section-monitoring-authentication
I think I'm running into the same issue as you. I'm trying to do Facebook authentication.
First, I'd like to clarify the reproduction steps for my issue.
My app is loaded on the client.
User clicks login with Facebook.
ref.authWithOAuthRedirect('facebook', ...) is called.
Client is redirected to Facebook and Facebook redirects client back to Firebase app
Despite successful authentication with Facebook, the callback passed to onAuth() is invoked (only once) with authData === null.
The callback passed to onAuth() is not invoked a second time with correct authData.
However, reloading the app causes the callback passed to onAuth to be invoked with correct authData. The reasons for this are not known to me but I suspect race condition.
Here's my workaround.
Before calling ref.authWithOAuthRedirect('facebook', ...) set yourself a flag in sessionStorage.
sessionStorage.reload = true;
ref.authWithOAuthRedirect('facebook', ...)
When the client is redirected to your app back from Facebook, you should be able to check for this flag and reload the page if necessary.
if (sessionStorage.reload) {
delete sessionStorage.reload;
setTimeout(function() {
location.reload();
}, 1000)
}
setTimeout(function() { ... }, 1000) helps fight the assumed race condition. I found 500 ms is insufficient time for the race condition to be resolved.
And one small gotcha: if you reload the page too soon, then authData remains null no matter how many times you reload the page.
Related
working on a .NET MAUI app and am trying to implement Firebase Authentication with the help of WebAuthenticator in MAUI. I get to the login form in a browser, but after logging in get the error
Unable to process request due to missing initial state. This may happen if browser sessionStorage is inaccessible or accidentally cleared.
This is the code that calls the authenticator
await client.SignInWithRedirectAsync(FirebaseProviderType.Google, async uri =>
{
var options = new WebAuthenticatorOptions
{
Url = new Uri(uri),
CallbackUrl = new Uri("com.companyname.myappname://callback/"),
PrefersEphemeralWebBrowserSession= true
};
var res = await WebAuthenticator.Default.AuthenticateAsync(options);
});
I think the problem could be the callback URL, but I'm not sure how to write it differently since I'm not using a backend API. Does anyone have any suggestions?
Thanks!
P.S. This happens with bost Firebase Google auth and Facebook login
You can try to clear the chrome browser data and reload the page to see if it works. This is a known problem of firebase. You can continue to follow up this github iissue: Unableto process request due to missing initial state.
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.
The Issue:
When my regular users using the standard email/password 'accounts-password' go to a login redirected page , they can enter the credentials and the router will proceed to render the requested page.
For example:
/private_page (requires login). The user attempts to go to http://foo.com/private_page they are presented with the Login page. The user enters username/password correctly and now the /private_page displays.
This works, what does NOT work...
Same example as above, but now the user is authenticating with Facebook 'accounts-facebook'. Everything is the same as before and the user has succesful login with Facebook and is able to get into my web application except the route never displays the /private_page. It stays on the authenticated /login page without showing the login template.
IN A NUTSHELL
How do I make the facebook authenticated users pass-thru and route to the requested route like the regular password based users operate ?
Iron Router Config:
Router.onBeforeAction(function () {
if (!Meteor.userId() && !Meteor.loggingIn()) {
this.redirect('login');
this.stop();
} else {
this.next();
}
},{except: ['login', 'contact, 'terms']});
Meteor packages:
accounts-password#1.3.6
accounts-facebook#1.2.0
service-configuration#1.0.11
useraccounts:bootstrap
useraccounts:iron-routing
I finally came to a solution.
After reviewing the Github issues for the Meteor package: (meteor-useraccounts)I found the exact issue I have been having.
https://github.com/meteor-useraccounts/core/issues/685
I spent too much time trying to make the hooks fire correctly for my usage of oAuth Facebook with Meteor. My final solution was too just connect directly into the Meteor method calls and create my own login, registration, password reset, etc forms.
The up-side is that I now have full control of the forms and I don't need to deal with extra package.
If anyone comes finds this posting and is having issues making 'meteor-useraccounts' fire hooks like the postSignUpHook , you may decide to just scrap the package and make your own user account templates and connect the logic to use the native Meteor methods.
This particular question I submitted was because the oAuth Facebook would login but I was unable to make it redirect to the originally requested route. The oAuth works and my user can login to my web app, but I cannot get them to the originally requested url.
How did I solve this:
lib/routes
Router.onBeforeAction(function () {
if (!Meteor.userId() && !Meteor.loggingIn()) {
originalUrl = this.originalUrl;
this.redirect('login');
this.stop();
} else {
this.next();
}
},{except: ['login', 'resetPwd', 'help'] });
The key point to take away from the snippet above is the global variable I declare originalUrl. This is using the routers this.originalUrl. This url contains the original url that the user entered and is captured the moment before the iron-router redirects to the login page.
Now on the login page I created my two different login methods using my own custom template. They both are using the Meteor.loginWithPassword and Meteor.loginWithFacebook methods.
client/login.js
'click #fb-login' : function(e){
e.preventDefault();
Meteor.loginWithFacebook({}, function(err){
if(err) {
// some error occured
}
else {
if(Router.current().route._path == "/login" && typeof originalUrl == "undefined")
Router.go('/');
else
Router.go(originalUrl);
}
});
}
Hope this helps anyone else who might come across this issue.
I'm using the froatsnook:shopify atmosphere package to create an embedded public app on Shopify. I currently have a couple issues:
1) Getting the access token from the "code" query parameter after a user authenticates. As it mentions in the docs here, I'm supposed to use authenticator.getPermanentAccessToken(code) but what I don't understand is how to get call authenticator if the "code" parameter appears on the callback route (at that point, the authenticator I instantiated on the client pre-auth route is out of scope).
2) The "oAuth" function callback is never called for some reason, even when assigning it to Shopify.onAuth on the server.
3) The difference between post_auth_uri and redirect_uri ?
// I call this during 'onBeforeAction' for iron-router
function beforeAuth (query) {
// is this necessary..?
console.assert(Meteor.isClient);
// get shop name like 'myshop' from 'myshop.shopify.com';
const shop = query.shop.substring(0, query.shop.indexOf('.'));
// use api_key stored in settings
var api_key = Meteor.settings.public.shopify.api_key;
// Prepare to authenticate
var authenticator = new Shopify.PublicAppOAuthAuthenticator({
shop: shop,
api_key: api_key,
keyset: 'default',
embedded_app_sdk: true,
redirect_uri: 'https://45a04f23.ngrok.com/testContent',
//post_auth_uri: ???
// This is doesn't seem to be getting
// called after clicking through the OAuth dialog
onAuth: function(access_token) {
ShopifyCredentials.insert({
shop: shop,
api_key: api_key,
access_token: access_token
});
}
});
// Should i use something different with iron-router?
location.href = authenticator.auth_uri;
// how do i get code in this scope???
// authenticator.getPermanentAccessToken(code);
}
There are a few issues with the way you are trying to set up the authenticator, although it's not really your fault because the way Scenario 3 works in the docs is not an 'out of the box' solution and requires a bunch of custom code, including your own handler (I can provide a gist if you REALLY want to build your own handler, but I suggest using the new server-side onAuth callback instead)
1. Specifying a redirect_uri overrides the package's default redirect_uri handler which is Meteor.absoluteUrl("/__shopify-auth").
So instead, completely remove redirect_uri and put your testContent url in post_auth_uri instead.
2. ShopifyCredentials does not exist in this package. If you want to use it that way, make sure you actually have defined a collection called 'ShopifyCredentials' and insert the record from the server, not the client. Note that you will still need to add a keyset on the server for the API methods to work. If you are using user accounts and would like to permanently store credentials, I suggest saving the credentials to the database and adding the keyset via a server-side onAuth callback.
3. authenticator.getPermanentAccessToken(code) isn't useful unless you are using your own handler. Instead, you can just get access_token from the onAuth callback.
Also keep in mind that if you ever need to reauthenticate from inside the embedded app, you need to use window.top.location.href to break out of the iframe.
If you want a complete, working boilerplate example with user accounts see my gist here:
Authentication with Accounts and Persistent Keysets
If you aren't using accounts, you can use this gist instead, but please note that you really need to come up with some way to check that the current client has permission to request the keyset for a given shop before going to production:
Authentication with Persistent Keysets
I'am trying to check the users permission (roles) in the routing (iron routing). I have a boolean property in the user which I set in the
Accounts.onCreateUser
e.g. isvalid = false. The first problem is that only username and id are exposed so I try too publish
Meteor.publish('userData',function() {
return Meteor.users.find({_id:this.userId},{fields:{'isvalid':1}});
});
In the router I check
onBeforeAction: function() {
if(!Meteor.user() || !Meteor.user().isvalid)
this.render('nopermission');
else
this.next();
}
It works but when I debug I can see that the onBeforeAction fires three times. First time the user is undefined, second time I have a user without the property isvalid and third time I have everything. In debug I can see the screen flash with the template "nopermission" but when I run it live it seems ok. I think I have done it wrong, how can I check the permission in the correct way? I know that this code runs in client and I plan to do the check even on the server and I suppose that the Meteor.user().isvalid works without any problems on the server.
Thanks for help